[perl #43425] local $[: fix scoping during parser error handling.
[p5sagit/p5-mst-13.2.git] / lib / CPANPLUS / inc.pm
1 package CPANPLUS::inc;
2
3 =head1 NAME
4
5 CPANPLUS::inc
6
7 =head1 DESCRIPTION
8
9 OBSOLETE
10
11 =cut
12
13 sub original_perl5opt   { $ENV{PERL5OPT}    };
14 sub original_perl5lib   { $ENV{PERL5LIB}    };
15 sub original_inc        { @INC              };
16
17 1;
18
19 __END__
20
21 use strict;
22 use vars        qw[$DEBUG $VERSION $ENABLE_INC_HOOK %LIMIT $QUIET];
23 use File::Spec  ();
24 use Config      ();
25
26 ### 5.6.1. nags about require + bareword otherwise ###
27 use lib ();
28
29 $QUIET              = 0;
30 $DEBUG              = 0;
31 %LIMIT              = ();
32
33 =pod
34
35 =head1 NAME
36
37 CPANPLUS::inc - runtime inclusion of privately bundled modules
38
39 =head1 SYNOPSIS
40
41     ### set up CPANPLUS::inc to do it's thing ###
42     BEGIN { use CPANPLUS::inc };
43
44     ### enable debugging ###
45     use CPANPLUS::inc qw[DEBUG];
46
47 =head1 DESCRIPTION
48
49 This module enables the use of the bundled modules in the
50 C<CPANPLUS/inc> directory of this package. These modules are bundled
51 to make sure C<CPANPLUS> is able to bootstrap itself. It will do the
52 following things:
53
54 =over 4
55
56 =item Put a coderef at the beginning of C<@INC>
57
58 This allows us to decide which module to load, and where to find it.
59 For details on what we do, see the C<INTERESTING MODULES> section below.
60 Also see the C<CAVEATS> section.
61
62 =item Add the full path to the C<CPANPLUS/inc> directory to C<$ENV{PERL5LIB>.
63
64 This allows us to find our bundled modules even if we spawn off a new
65 process. Although it's not able to do the selective loading as the
66 coderef in C<@INC> could, it's a good fallback.
67
68 =back
69
70 =head1 METHODS
71
72 =head2 CPANPLUS::inc->inc_path()
73
74 Returns the full path to the C<CPANPLUS/inc> directory.
75
76 =head2 CPANPLUS::inc->my_path()
77
78 Returns the full path to be added to C<@INC> to load
79 C<CPANPLUS::inc> from.
80
81 =head2 CPANPLUS::inc->installer_path()
82
83 Returns the full path to the C<CPANPLUS/inc/installers> directory.
84
85 =cut
86
87 {   my $ext     = '.pm';
88     my $file    = (join '/', split '::', __PACKAGE__) . $ext;
89
90     ### os specific file path, if you're not on unix
91     my $osfile  = File::Spec->catfile( split('::', __PACKAGE__) ) . $ext;
92
93     ### this returns a unixy path, compensate if you're on non-unix
94     my $path    = File::Spec->rel2abs(
95                         File::Spec->catfile( split '/', $INC{$file} )
96                     );
97
98     ### don't forget to quotemeta; win32 paths are special
99     my $qm_osfile = quotemeta $osfile;
100     my $path_to_me          = $path; $path_to_me    =~ s/$qm_osfile$//i;
101     my $path_to_inc         = $path; $path_to_inc   =~ s/$ext$//i;
102     my $path_to_installers  = File::Spec->catdir( $path_to_inc, 'installers' );
103
104     sub inc_path        { return $path_to_inc  }
105     sub my_path         { return $path_to_me   }
106     sub installer_path  { return $path_to_installers }
107 }
108
109 =head2 CPANPLUS::inc->original_perl5lib
110
111 Returns the value of $ENV{PERL5LIB} the way it was when C<CPANPLUS::inc>
112 got loaded.
113
114 =head2 CPANPLUS::inc->original_perl5opt
115
116 Returns the value of $ENV{PERL5OPT} the way it was when C<CPANPLUS::inc>
117 got loaded.
118
119 =head2 CPANPLUS::inc->original_inc
120
121 Returns the value of @INC the way it was when C<CPANPLUS::inc> got
122 loaded.
123
124 =head2 CPANPLUS::inc->limited_perl5opt(@modules);
125
126 Returns a string you can assign to C<$ENV{PERL5OPT}> to have a limited
127 include facility from C<CPANPLUS::inc>. It will roughly look like:
128
129     -I/path/to/cpanplus/inc -MCPANPLUS::inc=module1,module2
130
131 =cut
132
133 {   my $org_opt = $ENV{PERL5OPT};
134     my $org_lib = $ENV{PERL5LIB};
135     my @org_inc = @INC;
136
137     sub original_perl5opt   { $org_opt || ''};
138     sub original_perl5lib   { $org_lib || ''};
139     sub original_inc        { @org_inc, __PACKAGE__->my_path };
140
141     sub limited_perl5opt    {
142         my $pkg = shift;
143         my $lim = join ',', @_ or return;
144
145         ### -Icp::inc -Mcp::inc=mod1,mod2,mod3
146         my $opt =   '-I' . __PACKAGE__->my_path . ' ' .
147                     '-M' . __PACKAGE__ . "=$lim";
148
149         $opt .=     $Config::Config{'path_sep'} .
150                     CPANPLUS::inc->original_perl5opt
151                 if  CPANPLUS::inc->original_perl5opt;
152
153         return $opt;
154     }
155 }
156
157 =head2 CPANPLUS::inc->interesting_modules()
158
159 Returns a hashref with modules we're interested in, and the minimum
160 version we need to find.
161
162 It would looks something like this:
163
164     {   File::Fetch             => 0.06,
165         IPC::Cmd                => 0.22,
166         ....
167     }
168
169 =cut
170
171 {
172     my $map = {
173         ### used to have 0.80, but not it was never released by coral
174         ### 0.79 *should* be good enough for now... asked coral to 
175         ### release 0.80 on 10/3/2006
176         'IPC::Run'                  => '0.79', 
177         'File::Fetch'               => '0.07',
178         #'File::Spec'                => '0.82', # can't, need it ourselves...
179         'IPC::Cmd'                  => '0.24',
180         'Locale::Maketext::Simple'  => 0,
181         'Log::Message'              => 0,
182         'Module::Load'              => '0.10',
183         'Module::Load::Conditional' => '0.07',
184         'Params::Check'             => '0.22',
185         'Term::UI'                  => '0.05',
186         'Archive::Extract'          => '0.07',
187         'Archive::Tar'              => '1.23',
188         'IO::Zlib'                  => '1.04',
189         'Object::Accessor'          => '0.03',
190         'Module::CoreList'          => '1.97',
191         'Module::Pluggable'         => '2.4',
192         'Module::Loaded'            => 0,
193         #'Config::Auto'             => 0,   # not yet, not using it yet
194     };
195
196     sub interesting_modules { return $map; }
197 }
198
199
200 =head1 INTERESTING MODULES
201
202 C<CPANPLUS::inc> doesn't even bother to try find and find a module
203 it's not interested in. A list of I<interesting modules> can be
204 obtained using the C<interesting_modules> method described above.
205
206 Note that all subclassed modules of an C<interesting module> will
207 also be attempted to be loaded, but a version will not be checked.
208
209 When it however does encounter a module it is interested in, it will
210 do the following things:
211
212 =over 4
213
214 =item Loop over your @INC
215
216 And for every directory it finds there (skipping all non directories
217 -- see the C<CAVEATS> section), see if the module requested can be
218 found there.
219
220 =item Check the version on every suitable module found in @INC
221
222 After a list of modules has been gathered, the version of each of them
223 is checked to find the one with the highest version, and return that as
224 the module to C<use>.
225
226 This enables us to use a recent enough version from our own bundled
227 modules, but also to use a I<newer> module found in your path instead,
228 if it is present. Thus having access to bugfixed versions as they are
229 released.
230
231 If for some reason no satisfactory version could be found, a warning
232 will be emitted. See the C<DEBUG> section for more details on how to
233 find out exactly what C<CPANPLUS::inc> is doing.
234
235 =back
236
237 =cut
238
239 {   my $Loaded;
240     my %Cache;
241
242
243     ### returns the path to a certain module we found
244     sub path_to {
245         my $self    = shift;
246         my $mod     = shift or return;
247
248         ### find the directory
249         my $path    = $Cache{$mod}->[0][2] or return;
250
251         ### probe them explicitly for a special file, because the
252         ### dir we found the file in vs our own paths may point to the
253         ### same location, but might not pass an 'eq' test.
254
255         ### it's our inc-path
256         return __PACKAGE__->inc_path
257                 if -e File::Spec->catfile( $path, '.inc' );
258
259         ### it's our installer path
260         return __PACKAGE__->installer_path
261                 if -e File::Spec->catfile( $path, '.installers' );
262
263         ### it's just some dir...
264         return $path;
265     }
266
267     ### just a debug method
268     sub _show_cache { return \%Cache };
269
270     sub import {
271         my $pkg = shift;
272
273         ### filter DEBUG, and toggle the global
274         map { $LIMIT{$_} = 1 }  
275             grep {  /DEBUG/ ? ++$DEBUG && 0 : 
276                     /QUIET/ ? ++$QUIET && 0 :
277                     1 
278             } @_;
279         
280         ### only load once ###
281         return 1 if $Loaded++;
282
283         ### first, add our own private dir to the end of @INC:
284         {
285             push @INC,  __PACKAGE__->my_path, __PACKAGE__->inc_path,
286                         __PACKAGE__->installer_path;
287
288             ### XXX stop doing this, there's no need for it anymore;
289             ### none of the shell outs need to have this set anymore
290 #             ### add the path to this module to PERL5OPT in case
291 #             ### we spawn off some programs...
292 #             ### then add this module to be loaded in PERL5OPT...
293 #             {   local $^W;
294 #                 $ENV{'PERL5LIB'} .= $Config::Config{'path_sep'}
295 #                                  . __PACKAGE__->my_path
296 #                                  . $Config::Config{'path_sep'}
297 #                                  . __PACKAGE__->inc_path;
298 #
299 #                 $ENV{'PERL5OPT'} = '-M'. __PACKAGE__ . ' '
300 #                                  . ($ENV{'PERL5OPT'} || '');
301 #             }
302         }
303
304         ### next, find the highest version of a module that
305         ### we care about. very basic check, but will
306         ### have to do for now.
307         lib->import( sub {
308             my $path    = pop();                    # path to the pm
309             my $module  = $path or return;          # copy of the path, to munge
310             my @parts   = split qr!\\|/!, $path;    # dirs + file name; could be
311                                                     # win32 paths =/
312             my $file    = pop @parts;               # just the file name
313             my $map     = __PACKAGE__->interesting_modules;
314
315             ### translate file name to module name 
316             ### could contain win32 paths delimiters
317             $module =~ s!/|\\!::!g; $module =~ s/\.pm//i;
318
319             my $check_version; my $try;
320             ### does it look like a module we care about?
321             my ($interesting) = grep { $module =~ /^$_/ } keys %$map;
322             ++$try if $interesting;
323
324             ### do we need to check the version too?
325             ++$check_version if exists $map->{$module};
326
327             ### we don't care ###
328             unless( $try ) {
329                 warn __PACKAGE__ .": Not interested in '$module'\n" if $DEBUG;
330                 return;
331
332             ### we're not allowed
333             } elsif ( $try and keys %LIMIT ) {
334                 unless( grep { $module =~ /^$_/ } keys %LIMIT  ) {
335                     warn __PACKAGE__ .": Limits active, '$module' not allowed ".
336                                         "to be loaded" if $DEBUG;
337                     return;
338                 }
339             }
340
341             ### found filehandles + versions ###
342             my @found;
343             DIR: for my $dir (@INC) {
344                 next DIR unless -d $dir;
345
346                 ### get the full path to the module ###
347                 my $pm = File::Spec->catfile( $dir, @parts, $file );
348
349                 ### open the file if it exists ###
350                 if( -e $pm ) {
351                     my $fh;
352                     unless( open $fh, "$pm" ) {
353                         warn __PACKAGE__ .": Could not open '$pm': $!\n"
354                             if $DEBUG;
355                         next DIR;
356                     }
357
358                     my $found;
359                     ### XXX stolen from module::load::conditional ###
360                     while (local $_ = <$fh> ) {
361
362                         ### the following regexp comes from the
363                         ### ExtUtils::MakeMaker documentation.
364                         if ( /([\$*])(([\w\:\']*)\bVERSION)\b.*\=/ ) {
365
366                             ### this will eval the version in to $VERSION if it
367                             ### was declared as $VERSION in the module.
368                             ### else the result will be in $res.
369                             ### this is a fix on skud's Module::InstalledVersion
370
371                             local $VERSION;
372                             my $res = eval $_;
373
374                             ### default to '0.0' if there REALLY is no version
375                             ### all to satisfy warnings
376                             $found = $VERSION || $res || '0.0';
377
378                             ### found what we came for
379                             last if $found;
380                         }
381                     }
382
383                     ### no version defined at all? ###
384                     $found ||= '0.0';
385
386                     warn __PACKAGE__ .": Found match for '$module' in '$dir' "
387                                      ."with version '$found'\n" if $DEBUG;
388
389                     ### reset the position of the filehandle ###
390                     seek $fh, 0, 0;
391
392                     ### store the found version + filehandle it came from ###
393                     push @found, [ $found, $fh, $dir, $pm ];
394                 }
395
396             } # done looping over all the dirs
397
398             ### nothing found? ###
399             unless (@found) {
400                 warn __PACKAGE__ .": Unable to find any module named "
401                                     . "'$module'\n" if $DEBUG;
402                 return;
403             }
404
405             ### find highest version
406             ### or the one in the same dir as a base module already loaded
407             ### or otherwise, the one not bundled
408             ### or otherwise the newest
409             my @sorted = sort {
410                             _vcmp($b->[0], $a->[0])                  ||
411                             ($Cache{$interesting}
412                                 ?($b->[2] eq $Cache{$interesting}->[0][2]) <=>
413                                  ($a->[2] eq $Cache{$interesting}->[0][2])
414                                 : 0 )                               ||
415                             (($a->[2] eq __PACKAGE__->inc_path) <=>
416                              ($b->[2] eq __PACKAGE__->inc_path))    ||
417                             (-M $a->[3] <=> -M $b->[3])
418                           } @found;
419
420             warn __PACKAGE__ .": Best match for '$module' is found in "
421                              ."'$sorted[0][2]' with version '$sorted[0][0]'\n"
422                     if $DEBUG;
423
424             if( $check_version and 
425                 not (_vcmp($sorted[0][0], $map->{$module}) >= 0) 
426             ) {
427                 warn __PACKAGE__ .": Cannot find high enough version for "
428                                  ."'$module' -- need '$map->{$module}' but "
429                                  ."only found '$sorted[0][0]'. Returning "
430                                  ."highest found version but this may cause "
431                                  ."problems\n" unless $QUIET;
432             };
433
434             ### right, so that damn )#$(*@#)(*@#@ Module::Build makes
435             ### assumptions about the environment (especially its own tests)
436             ### and blows up badly if it's loaded via CP::inc :(
437             ### so, if we find a newer version on disk (which would happen when
438             ### upgrading or having upgraded, just pretend we didn't find it,
439             ### let it be loaded via the 'normal' way.
440             ### can't even load the *proper* one via our CP::inc, as it will
441             ### get upset just over the fact it's loaded via a non-standard way
442             if( $module =~ /^Module::Build/ and
443                 $sorted[0][2] ne __PACKAGE__->inc_path and
444                 $sorted[0][2] ne __PACKAGE__->installer_path
445             ) {
446                 warn __PACKAGE__ .": Found newer version of 'Module::Build::*' "
447                                  ."elsewhere in your path. Pretending to not "
448                                  ."have found it\n" if $DEBUG;
449                 return;
450             }
451
452             ### store what we found for this module
453             $Cache{$module} = \@sorted;
454
455             ### best matching filehandle ###
456             return $sorted[0][1];
457         } );
458     }
459 }
460
461 ### XXX copied from C::I::Utils, so there's no circular require here!
462 sub _vcmp {
463     my ($x, $y) = @_;
464     s/_//g foreach $x, $y;
465     return $x <=> $y;
466 }
467
468 =pod
469
470 =head1 DEBUG
471
472 Since this module does C<Clever Things> to your search path, it might
473 be nice sometimes to figure out what it's doing, if things don't work
474 as expected. You can enable a debug trace by calling the module like
475 this:
476
477     use CPANPLUS::inc 'DEBUG';
478
479 This will show you what C<CPANPLUS::inc> is doing, which might look
480 something like this:
481
482     CPANPLUS::inc: Found match for 'Params::Check' in
483     '/opt/lib/perl5/site_perl/5.8.3' with version '0.07'
484     CPANPLUS::inc: Found match for 'Params::Check' in
485     '/my/private/lib/CPANPLUS/inc' with version '0.21'
486     CPANPLUS::inc: Best match for 'Params::Check' is found in
487     '/my/private/lib/CPANPLUS/inc' with version '0.21'
488
489 =head1 CAVEATS
490
491 This module has 2 major caveats, that could lead to unexpected
492 behaviour. But currently I don't know how to fix them, Suggestions
493 are much welcomed.
494
495 =over 4
496
497 =item On multiple C<use lib> calls, our coderef may not be the first in @INC
498
499 If this happens, although unlikely in most situations and not happening
500 when calling the shell directly, this could mean that a lower (too low)
501 versioned module is loaded, which might cause failures in the
502 application.
503
504 =item Non-directories in @INC
505
506 Non-directories are right now skipped by CPANPLUS::inc. They could of
507 course lead us to newer versions of a module, but it's too tricky to
508 verify if they would. Therefor they are skipped. In the worst case
509 scenario we'll find the sufficing version bundled with CPANPLUS.
510
511
512 =cut
513
514 1;
515
516 # Local variables:
517 # c-indentation-style: bsd
518 # c-basic-offset: 4
519 # indent-tabs-mode: nil
520 # End:
521 # vim: expandtab shiftwidth=4:
522