summaryrefslogtreecommitdiff
path: root/lib/CPANPLUS/inc.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/CPANPLUS/inc.pm')
-rw-r--r--lib/CPANPLUS/inc.pm522
1 files changed, 522 insertions, 0 deletions
diff --git a/lib/CPANPLUS/inc.pm b/lib/CPANPLUS/inc.pm
new file mode 100644
index 0000000000..000a0ce92a
--- /dev/null
+++ b/lib/CPANPLUS/inc.pm
@@ -0,0 +1,522 @@
+package CPANPLUS::inc;
+
+=head1 NAME
+
+CPANPLUS::inc
+
+=head1 DESCRIPTION
+
+OBSOLETE
+
+=cut
+
+sub original_perl5opt { $ENV{PERL5OPT} };
+sub original_perl5lib { $ENV{PERL5LIB} };
+sub original_inc { @INC };
+
+1;
+
+__END__
+
+use strict;
+use vars qw[$DEBUG $VERSION $ENABLE_INC_HOOK %LIMIT $QUIET];
+use File::Spec ();
+use Config ();
+
+### 5.6.1. nags about require + bareword otherwise ###
+use lib ();
+
+$QUIET = 0;
+$DEBUG = 0;
+%LIMIT = ();
+
+=pod
+
+=head1 NAME
+
+CPANPLUS::inc - runtime inclusion of privately bundled modules
+
+=head1 SYNOPSIS
+
+ ### set up CPANPLUS::inc to do it's thing ###
+ BEGIN { use CPANPLUS::inc };
+
+ ### enable debugging ###
+ use CPANPLUS::inc qw[DEBUG];
+
+=head1 DESCRIPTION
+
+This module enables the use of the bundled modules in the
+C<CPANPLUS/inc> directory of this package. These modules are bundled
+to make sure C<CPANPLUS> is able to bootstrap itself. It will do the
+following things:
+
+=over 4
+
+=item Put a coderef at the beginning of C<@INC>
+
+This allows us to decide which module to load, and where to find it.
+For details on what we do, see the C<INTERESTING MODULES> section below.
+Also see the C<CAVEATS> section.
+
+=item Add the full path to the C<CPANPLUS/inc> directory to C<$ENV{PERL5LIB>.
+
+This allows us to find our bundled modules even if we spawn off a new
+process. Although it's not able to do the selective loading as the
+coderef in C<@INC> could, it's a good fallback.
+
+=back
+
+=head1 METHODS
+
+=head2 CPANPLUS::inc->inc_path()
+
+Returns the full path to the C<CPANPLUS/inc> directory.
+
+=head2 CPANPLUS::inc->my_path()
+
+Returns the full path to be added to C<@INC> to load
+C<CPANPLUS::inc> from.
+
+=head2 CPANPLUS::inc->installer_path()
+
+Returns the full path to the C<CPANPLUS/inc/installers> directory.
+
+=cut
+
+{ my $ext = '.pm';
+ my $file = (join '/', split '::', __PACKAGE__) . $ext;
+
+ ### os specific file path, if you're not on unix
+ my $osfile = File::Spec->catfile( split('::', __PACKAGE__) ) . $ext;
+
+ ### this returns a unixy path, compensate if you're on non-unix
+ my $path = File::Spec->rel2abs(
+ File::Spec->catfile( split '/', $INC{$file} )
+ );
+
+ ### don't forget to quotemeta; win32 paths are special
+ my $qm_osfile = quotemeta $osfile;
+ my $path_to_me = $path; $path_to_me =~ s/$qm_osfile$//i;
+ my $path_to_inc = $path; $path_to_inc =~ s/$ext$//i;
+ my $path_to_installers = File::Spec->catdir( $path_to_inc, 'installers' );
+
+ sub inc_path { return $path_to_inc }
+ sub my_path { return $path_to_me }
+ sub installer_path { return $path_to_installers }
+}
+
+=head2 CPANPLUS::inc->original_perl5lib
+
+Returns the value of $ENV{PERL5LIB} the way it was when C<CPANPLUS::inc>
+got loaded.
+
+=head2 CPANPLUS::inc->original_perl5opt
+
+Returns the value of $ENV{PERL5OPT} the way it was when C<CPANPLUS::inc>
+got loaded.
+
+=head2 CPANPLUS::inc->original_inc
+
+Returns the value of @INC the way it was when C<CPANPLUS::inc> got
+loaded.
+
+=head2 CPANPLUS::inc->limited_perl5opt(@modules);
+
+Returns a string you can assign to C<$ENV{PERL5OPT}> to have a limited
+include facility from C<CPANPLUS::inc>. It will roughly look like:
+
+ -I/path/to/cpanplus/inc -MCPANPLUS::inc=module1,module2
+
+=cut
+
+{ my $org_opt = $ENV{PERL5OPT};
+ my $org_lib = $ENV{PERL5LIB};
+ my @org_inc = @INC;
+
+ sub original_perl5opt { $org_opt || ''};
+ sub original_perl5lib { $org_lib || ''};
+ sub original_inc { @org_inc, __PACKAGE__->my_path };
+
+ sub limited_perl5opt {
+ my $pkg = shift;
+ my $lim = join ',', @_ or return;
+
+ ### -Icp::inc -Mcp::inc=mod1,mod2,mod3
+ my $opt = '-I' . __PACKAGE__->my_path . ' ' .
+ '-M' . __PACKAGE__ . "=$lim";
+
+ $opt .= $Config::Config{'path_sep'} .
+ CPANPLUS::inc->original_perl5opt
+ if CPANPLUS::inc->original_perl5opt;
+
+ return $opt;
+ }
+}
+
+=head2 CPANPLUS::inc->interesting_modules()
+
+Returns a hashref with modules we're interested in, and the minimum
+version we need to find.
+
+It would looks something like this:
+
+ { File::Fetch => 0.06,
+ IPC::Cmd => 0.22,
+ ....
+ }
+
+=cut
+
+{
+ my $map = {
+ ### used to have 0.80, but not it was never released by coral
+ ### 0.79 *should* be good enough for now... asked coral to
+ ### release 0.80 on 10/3/2006
+ 'IPC::Run' => '0.79',
+ 'File::Fetch' => '0.07',
+ #'File::Spec' => '0.82', # can't, need it ourselves...
+ 'IPC::Cmd' => '0.24',
+ 'Locale::Maketext::Simple' => 0,
+ 'Log::Message' => 0,
+ 'Module::Load' => '0.10',
+ 'Module::Load::Conditional' => '0.07',
+ 'Params::Check' => '0.22',
+ 'Term::UI' => '0.05',
+ 'Archive::Extract' => '0.07',
+ 'Archive::Tar' => '1.23',
+ 'IO::Zlib' => '1.04',
+ 'Object::Accessor' => '0.03',
+ 'Module::CoreList' => '1.97',
+ 'Module::Pluggable' => '2.4',
+ 'Module::Loaded' => 0,
+ #'Config::Auto' => 0, # not yet, not using it yet
+ };
+
+ sub interesting_modules { return $map; }
+}
+
+
+=head1 INTERESTING MODULES
+
+C<CPANPLUS::inc> doesn't even bother to try find and find a module
+it's not interested in. A list of I<interesting modules> can be
+obtained using the C<interesting_modules> method described above.
+
+Note that all subclassed modules of an C<interesting module> will
+also be attempted to be loaded, but a version will not be checked.
+
+When it however does encounter a module it is interested in, it will
+do the following things:
+
+=over 4
+
+=item Loop over your @INC
+
+And for every directory it finds there (skipping all non directories
+-- see the C<CAVEATS> section), see if the module requested can be
+found there.
+
+=item Check the version on every suitable module found in @INC
+
+After a list of modules has been gathered, the version of each of them
+is checked to find the one with the highest version, and return that as
+the module to C<use>.
+
+This enables us to use a recent enough version from our own bundled
+modules, but also to use a I<newer> module found in your path instead,
+if it is present. Thus having access to bugfixed versions as they are
+released.
+
+If for some reason no satisfactory version could be found, a warning
+will be emitted. See the C<DEBUG> section for more details on how to
+find out exactly what C<CPANPLUS::inc> is doing.
+
+=back
+
+=cut
+
+{ my $Loaded;
+ my %Cache;
+
+
+ ### returns the path to a certain module we found
+ sub path_to {
+ my $self = shift;
+ my $mod = shift or return;
+
+ ### find the directory
+ my $path = $Cache{$mod}->[0][2] or return;
+
+ ### probe them explicitly for a special file, because the
+ ### dir we found the file in vs our own paths may point to the
+ ### same location, but might not pass an 'eq' test.
+
+ ### it's our inc-path
+ return __PACKAGE__->inc_path
+ if -e File::Spec->catfile( $path, '.inc' );
+
+ ### it's our installer path
+ return __PACKAGE__->installer_path
+ if -e File::Spec->catfile( $path, '.installers' );
+
+ ### it's just some dir...
+ return $path;
+ }
+
+ ### just a debug method
+ sub _show_cache { return \%Cache };
+
+ sub import {
+ my $pkg = shift;
+
+ ### filter DEBUG, and toggle the global
+ map { $LIMIT{$_} = 1 }
+ grep { /DEBUG/ ? ++$DEBUG && 0 :
+ /QUIET/ ? ++$QUIET && 0 :
+ 1
+ } @_;
+
+ ### only load once ###
+ return 1 if $Loaded++;
+
+ ### first, add our own private dir to the end of @INC:
+ {
+ push @INC, __PACKAGE__->my_path, __PACKAGE__->inc_path,
+ __PACKAGE__->installer_path;
+
+ ### XXX stop doing this, there's no need for it anymore;
+ ### none of the shell outs need to have this set anymore
+# ### add the path to this module to PERL5OPT in case
+# ### we spawn off some programs...
+# ### then add this module to be loaded in PERL5OPT...
+# { local $^W;
+# $ENV{'PERL5LIB'} .= $Config::Config{'path_sep'}
+# . __PACKAGE__->my_path
+# . $Config::Config{'path_sep'}
+# . __PACKAGE__->inc_path;
+#
+# $ENV{'PERL5OPT'} = '-M'. __PACKAGE__ . ' '
+# . ($ENV{'PERL5OPT'} || '');
+# }
+ }
+
+ ### next, find the highest version of a module that
+ ### we care about. very basic check, but will
+ ### have to do for now.
+ lib->import( sub {
+ my $path = pop(); # path to the pm
+ my $module = $path or return; # copy of the path, to munge
+ my @parts = split qr!\\|/!, $path; # dirs + file name; could be
+ # win32 paths =/
+ my $file = pop @parts; # just the file name
+ my $map = __PACKAGE__->interesting_modules;
+
+ ### translate file name to module name
+ ### could contain win32 paths delimiters
+ $module =~ s!/|\\!::!g; $module =~ s/\.pm//i;
+
+ my $check_version; my $try;
+ ### does it look like a module we care about?
+ my ($interesting) = grep { $module =~ /^$_/ } keys %$map;
+ ++$try if $interesting;
+
+ ### do we need to check the version too?
+ ++$check_version if exists $map->{$module};
+
+ ### we don't care ###
+ unless( $try ) {
+ warn __PACKAGE__ .": Not interested in '$module'\n" if $DEBUG;
+ return;
+
+ ### we're not allowed
+ } elsif ( $try and keys %LIMIT ) {
+ unless( grep { $module =~ /^$_/ } keys %LIMIT ) {
+ warn __PACKAGE__ .": Limits active, '$module' not allowed ".
+ "to be loaded" if $DEBUG;
+ return;
+ }
+ }
+
+ ### found filehandles + versions ###
+ my @found;
+ DIR: for my $dir (@INC) {
+ next DIR unless -d $dir;
+
+ ### get the full path to the module ###
+ my $pm = File::Spec->catfile( $dir, @parts, $file );
+
+ ### open the file if it exists ###
+ if( -e $pm ) {
+ my $fh;
+ unless( open $fh, "$pm" ) {
+ warn __PACKAGE__ .": Could not open '$pm': $!\n"
+ if $DEBUG;
+ next DIR;
+ }
+
+ my $found;
+ ### XXX stolen from module::load::conditional ###
+ while (local $_ = <$fh> ) {
+
+ ### the following regexp comes from the
+ ### ExtUtils::MakeMaker documentation.
+ if ( /([\$*])(([\w\:\']*)\bVERSION)\b.*\=/ ) {
+
+ ### this will eval the version in to $VERSION if it
+ ### was declared as $VERSION in the module.
+ ### else the result will be in $res.
+ ### this is a fix on skud's Module::InstalledVersion
+
+ local $VERSION;
+ my $res = eval $_;
+
+ ### default to '0.0' if there REALLY is no version
+ ### all to satisfy warnings
+ $found = $VERSION || $res || '0.0';
+
+ ### found what we came for
+ last if $found;
+ }
+ }
+
+ ### no version defined at all? ###
+ $found ||= '0.0';
+
+ warn __PACKAGE__ .": Found match for '$module' in '$dir' "
+ ."with version '$found'\n" if $DEBUG;
+
+ ### reset the position of the filehandle ###
+ seek $fh, 0, 0;
+
+ ### store the found version + filehandle it came from ###
+ push @found, [ $found, $fh, $dir, $pm ];
+ }
+
+ } # done looping over all the dirs
+
+ ### nothing found? ###
+ unless (@found) {
+ warn __PACKAGE__ .": Unable to find any module named "
+ . "'$module'\n" if $DEBUG;
+ return;
+ }
+
+ ### find highest version
+ ### or the one in the same dir as a base module already loaded
+ ### or otherwise, the one not bundled
+ ### or otherwise the newest
+ my @sorted = sort {
+ _vcmp($b->[0], $a->[0]) ||
+ ($Cache{$interesting}
+ ?($b->[2] eq $Cache{$interesting}->[0][2]) <=>
+ ($a->[2] eq $Cache{$interesting}->[0][2])
+ : 0 ) ||
+ (($a->[2] eq __PACKAGE__->inc_path) <=>
+ ($b->[2] eq __PACKAGE__->inc_path)) ||
+ (-M $a->[3] <=> -M $b->[3])
+ } @found;
+
+ warn __PACKAGE__ .": Best match for '$module' is found in "
+ ."'$sorted[0][2]' with version '$sorted[0][0]'\n"
+ if $DEBUG;
+
+ if( $check_version and
+ not (_vcmp($sorted[0][0], $map->{$module}) >= 0)
+ ) {
+ warn __PACKAGE__ .": Cannot find high enough version for "
+ ."'$module' -- need '$map->{$module}' but "
+ ."only found '$sorted[0][0]'. Returning "
+ ."highest found version but this may cause "
+ ."problems\n" unless $QUIET;
+ };
+
+ ### right, so that damn )#$(*@#)(*@#@ Module::Build makes
+ ### assumptions about the environment (especially its own tests)
+ ### and blows up badly if it's loaded via CP::inc :(
+ ### so, if we find a newer version on disk (which would happen when
+ ### upgrading or having upgraded, just pretend we didn't find it,
+ ### let it be loaded via the 'normal' way.
+ ### can't even load the *proper* one via our CP::inc, as it will
+ ### get upset just over the fact it's loaded via a non-standard way
+ if( $module =~ /^Module::Build/ and
+ $sorted[0][2] ne __PACKAGE__->inc_path and
+ $sorted[0][2] ne __PACKAGE__->installer_path
+ ) {
+ warn __PACKAGE__ .": Found newer version of 'Module::Build::*' "
+ ."elsewhere in your path. Pretending to not "
+ ."have found it\n" if $DEBUG;
+ return;
+ }
+
+ ### store what we found for this module
+ $Cache{$module} = \@sorted;
+
+ ### best matching filehandle ###
+ return $sorted[0][1];
+ } );
+ }
+}
+
+### XXX copied from C::I::Utils, so there's no circular require here!
+sub _vcmp {
+ my ($x, $y) = @_;
+ s/_//g foreach $x, $y;
+ return $x <=> $y;
+}
+
+=pod
+
+=head1 DEBUG
+
+Since this module does C<Clever Things> to your search path, it might
+be nice sometimes to figure out what it's doing, if things don't work
+as expected. You can enable a debug trace by calling the module like
+this:
+
+ use CPANPLUS::inc 'DEBUG';
+
+This will show you what C<CPANPLUS::inc> is doing, which might look
+something like this:
+
+ CPANPLUS::inc: Found match for 'Params::Check' in
+ '/opt/lib/perl5/site_perl/5.8.3' with version '0.07'
+ CPANPLUS::inc: Found match for 'Params::Check' in
+ '/my/private/lib/CPANPLUS/inc' with version '0.21'
+ CPANPLUS::inc: Best match for 'Params::Check' is found in
+ '/my/private/lib/CPANPLUS/inc' with version '0.21'
+
+=head1 CAVEATS
+
+This module has 2 major caveats, that could lead to unexpected
+behaviour. But currently I don't know how to fix them, Suggestions
+are much welcomed.
+
+=over 4
+
+=item On multiple C<use lib> calls, our coderef may not be the first in @INC
+
+If this happens, although unlikely in most situations and not happening
+when calling the shell directly, this could mean that a lower (too low)
+versioned module is loaded, which might cause failures in the
+application.
+
+=item Non-directories in @INC
+
+Non-directories are right now skipped by CPANPLUS::inc. They could of
+course lead us to newer versions of a module, but it's too tricky to
+verify if they would. Therefor they are skipped. In the worst case
+scenario we'll find the sufficing version bundled with CPANPLUS.
+
+
+=cut
+
+1;
+
+# Local variables:
+# c-indentation-style: bsd
+# c-basic-offset: 4
+# indent-tabs-mode: nil
+# End:
+# vim: expandtab shiftwidth=4:
+