This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Upgrade to Math::BigInt 1.44 from Tels and
[perl5.git] / lib / CPAN.pm
index ed48d68..de1158d 100644 (file)
@@ -1,11 +1,11 @@
 # -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*-
 package CPAN;
-$VERSION = '1.59_51';
-# $Id: CPAN.pm,v 1.381 2000/12/01 08:13:05 k Exp $
+$VERSION = '1.59_56';
+# $Id: CPAN.pm,v 1.385 2001/02/09 21:37:57 k Exp $
 
 # only used during development:
 $Revision = "";
-# $Revision = "[".substr(q$Revision: 1.381 $, 10)."]";
+# $Revision = "[".substr(q$Revision: 1.385 $, 10)."]";
 
 use Carp ();
 use Config ();
@@ -25,6 +25,8 @@ use File::Spec;
 no lib "."; # we need to run chdir all over and we would get at wrong
             # libraries there
 
+require Mac::BuildTools if $^O eq 'MacOS';
+
 END { $End++; &cleanup; }
 
 %CPAN::DEBUG = qw[
@@ -229,6 +231,10 @@ package CPAN::FTP;
 use vars qw($Ua $Thesite $Themethod);
 @CPAN::FTP::ISA = qw(CPAN::Debug);
 
+package CPAN::LWP::UserAgent;
+use vars qw(@ISA $USER $PASSWD $SETUPDONE);
+# we delay requiring LWP::UserAgent and setting up inheritence until we need it
+
 package CPAN::Complete;
 @CPAN::Complete::ISA = qw(CPAN::Debug);
 @CPAN::Complete::COMMANDS = sort qw(
@@ -238,10 +244,10 @@ package CPAN::Complete;
 ) unless @CPAN::Complete::COMMANDS;
 
 package CPAN::Index;
-use vars qw($last_time $date_of_03);
+use vars qw($LAST_TIME $DATE_OF_02 $DATE_OF_03);
 @CPAN::Index::ISA = qw(CPAN::Debug);
-$last_time ||= 0;
-$date_of_03 ||= 0;
+$LAST_TIME ||= 0;
+$DATE_OF_03 ||= 0;
 # use constant PROTOCOL => "2.0"; # outcommented to avoid warning on upgrade from 1.57
 sub PROTOCOL { 2.0 }
 
@@ -708,10 +714,10 @@ sub has_inst {
 
 }) unless $Have_warned->{"Net::FTP"}++;
        sleep 3;
-    } elsif ($mod eq "MD5"){
+    } elsif ($mod eq "Digest::MD5"){
        $CPAN::Frontend->myprint(qq{
-  CPAN: MD5 security checks disabled because MD5 not installed.
-  Please consider installing the MD5 module.
+  CPAN: MD5 security checks disabled because Digest::MD5 not installed.
+  Please consider installing the Digest::MD5 module.
 
 });
        sleep 2;
@@ -1249,19 +1255,17 @@ sub h {
     } else {
        $CPAN::Frontend->myprint(q{
 Display Information
- a                                    authors
- b         string           display   bundles
- d         or               info      distributions
- m         /regex/          about     modules
- i         or                         anything of above
- r         none             reinstall recommendations
- u                          uninstalled distributions
+ command  argument          description
+ a,b,d,m  WORD or /REGEXP/  about authors, bundles, distributions, modules
+ i        WORD or /REGEXP/  about anything of above
+ r        NONE              reinstall recommendations
+ ls       AUTHOR            about files in the author's directory
 
 Download, Test, Make, Install...
  get                        download
  make                       make (implies get)
- test      modules,         make test (implies make)
- install   dists, bundles   make install (implies test)
+ test      MODULES,         make test (implies make)
+ install   DISTS, BUNDLES   make install (implies test)
  clean                      make clean
  look                       open subshell in these dists' directories
  readme                     display these dists' README files
@@ -1281,7 +1285,7 @@ sub a {
   my($self,@arg) = @_;
   # authors are always UPPERCASE
   for (@arg) {
-    $_ = uc $_;
+    $_ = uc $_ unless /=/;
   }
   $CPAN::Frontend->myprint($self->format_result('Author',@arg));
 }
@@ -1289,10 +1293,15 @@ sub a {
 #-> sub CPAN::Shell::ls ;
 sub ls      {
     my($self,@arg) = @_;
+    my @accept;
     for (@arg) {
-        $_ = uc $_;
+        unless (/^[A-Z\-]+$/i) {
+            $CPAN::Frontend->mywarn("ls command rejects argument $_: not an author");
+            next;
+        }
+        push @accept, uc $_;
     }
-    for my $a (@arg){
+    for my $a (@accept){
         my $author = $self->expand('Author',$a) or die "No author found for $a";
         $author->ls;
     }
@@ -1310,7 +1319,7 @@ sub local_bundles {
             if ($dh = DirHandle->new($bdir)) { # may fail
                 my($entry);
                 for $entry ($dh->read) {
-                    next if $entry =~ /^\./; # 
+                    next if $entry =~ /^\./;
                     if (-d MM->catdir($bdir,$entry)){
                         push @bbase, "$bbase\::$entry";
                     } else {
@@ -1963,7 +1972,7 @@ sub rematein {
        }
        if (ref $obj) {
             $obj->color_cmd_tmps(0,1);
-            CPAN::Queue->new($s);
+            CPAN::Queue->new($obj->id);
             push @qcopy, $obj;
        } elsif ($CPAN::META->exists('CPAN::Author',$s)) {
            $obj = $CPAN::META->instance('CPAN::Author',$s);
@@ -2054,6 +2063,60 @@ sub look   { shift->rematein('look',@_); }
 #-> sub CPAN::Shell::cvs_import ;
 sub cvs_import   { shift->rematein('cvs_import',@_); }
 
+package CPAN::LWP::UserAgent;
+
+sub config {
+    return if $SETUPDONE;
+    if ($CPAN::META->has_usable('LWP::UserAgent')) {
+        require LWP::UserAgent;
+        @ISA = qw(Exporter LWP::UserAgent);
+        $SETUPDONE++;
+    } else {
+        $CPAN::Frontent->mywarn("LWP::UserAgent not available\n");
+    }
+}
+
+sub get_basic_credentials {
+    my($self, $realm, $uri, $proxy) = @_;
+    return unless $proxy;
+    if ($USER && $PASSWD) {
+    } elsif (defined $CPAN::Config->{proxy_user} &&
+        defined $CPAN::Config->{proxy_pass}) {
+        $USER = $CPAN::Config->{proxy_user};
+        $PASSWD = $CPAN::Config->{proxy_pass};
+    } else {
+        require ExtUtils::MakeMaker;
+        ExtUtils::MakeMaker->import(qw(prompt));
+        $USER = prompt("Proxy authentication needed!
+ (Note: to permanently configure username and password run
+   o conf proxy_user your_username
+   o conf proxy_pass your_password
+ )\nUsername:");
+        if ($CPAN::META->has_inst("Term::ReadKey")) {
+            Term::ReadKey::ReadMode("noecho");
+        } else {
+            $CPAN::Frontend->mywarn("Warning: Term::ReadKey seems not to be available, your password will be echoed to the terminal!\n");
+        }
+        $PASSWD = prompt("Password:");
+        if ($CPAN::META->has_inst("Term::ReadKey")) {
+            Term::ReadKey::ReadMode("restore");
+        }
+        $CPAN::Frontend->myprint("\n\n");
+    }
+    return($USER,$PASSWD);
+}
+
+sub mirror {
+    my($self,$url,$aslocal) = @_;
+    my $result = $self->SUPER::mirror($url,$aslocal);
+    if ($result->code == 407) {
+        undef $USER;
+        undef $PASSWD;
+        $result = $self->SUPER::mirror($url,$aslocal);
+    }
+    $result;
+}
+
 package CPAN::FTP;
 
 #-> sub CPAN::FTP::ftp_get ;
@@ -2163,9 +2226,10 @@ sub localize {
     # Inheritance is not easier to manage than a few if/else branches
     if ($CPAN::META->has_usable('LWP::UserAgent')) {
        unless ($Ua) {
-           eval {$Ua = LWP::UserAgent->new;}; # Why is has_usable still not fit enough?
+            CPAN::LWP::UserAgent->config;
+           eval {$Ua = CPAN::LWP::UserAgent->new;}; # Why is has_usable still not fit enough?
             if ($@) {
-                $CPAN::Frontent->mywarn("LWP::UserAgent->new dies with $@")
+                $CPAN::Frontent->mywarn("CPAN::LWP::UserAgent->new dies with $@")
                     if $CPAN::DEBUG;
             } else {
                 my($var);
@@ -2173,6 +2237,20 @@ sub localize {
                     if $var = $CPAN::Config->{ftp_proxy} || $ENV{ftp_proxy};
                 $Ua->proxy('http', $var)
                     if $var = $CPAN::Config->{http_proxy} || $ENV{http_proxy};
+
+
+# >>>>> On Wed, 13 Dec 2000 09:21:34 -0500, "Robison, Jonathon (J.M.)" <jrobiso2@visteon.com> said:
+# 
+#  > I note that although CPAN.pm can use proxies, it doesn't seem equipped to
+#  > use ones that require basic autorization.
+#  
+#  > Example of when I use it manually in my own stuff:
+#  
+#  > $ua->proxy(['http','ftp'], http://my.proxy.server:83');
+#  > $req->proxy_authorization_basic("username","password");
+#  > $res = $ua->request($req);
+# 
+
                 $Ua->no_proxy($var)
                     if $var = $CPAN::Config->{no_proxy} || $ENV{no_proxy};
             }
@@ -2275,6 +2353,7 @@ sub hosteasy {
                                                     # meant
                                                     # file://localhost
                $l =~ s|^/||s unless -f $l;         # e.g. /P:
+               $self->debug("without URI::URL we try local file $l") if $CPAN::DEBUG;
            }
            if ( -f $l && -r _) {
                $Thesite = $i;
@@ -2295,8 +2374,11 @@ sub hosteasy {
   $url
 ");
          unless ($Ua) {
-           require LWP::UserAgent;
-           $Ua = LWP::UserAgent->new;
+              CPAN::LWP::UserAgent->config;
+              eval { $Ua = CPAN::LWP::UserAgent->new; };
+              if ($@) {
+                  $CPAN::Frontent->mywarn("CPAN::LWP::UserAgent->new dies with $@");
+              }
          }
          my $res = $Ua->mirror($url, $aslocal);
          if ($res->is_success) {
@@ -2318,12 +2400,17 @@ sub hosteasy {
              return $aslocal;
            }
          } else {
+              $CPAN::Frontend->myprint(sprintf(
+                                               "LWP failed with code[%s] message[%s]\n",
+                                               $res->code,
+                                               $res->message,
+                                              ));
            # Alan Burlison informed me that in firewall environments
            # Net::FTP can still succeed where LWP fails. So we do not
            # skip Net::FTP anymore when LWP is available.
          }
        } else {
-         $self->debug("LWP not installed") if $CPAN::DEBUG;
+            $CPAN::Frontend->myprint("LWP not available\n");
        }
         return if $CPAN::Signal;
        if ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) {
@@ -2763,7 +2850,7 @@ sub cpl {
     my @return;
     if ($pos == 0) {
        @return = grep /^$word/, @CPAN::Complete::COMMANDS;
-    } elsif ( $line !~ /^[\!abcdhimorutl]/ ) {
+    } elsif ( $line !~ /^[\!abcdghimorutl]/ ) {
        @return = ();
     } elsif ($line =~ /^(a|ls)\s/) {
        @return = cplx('CPAN::Author',uc($word));
@@ -2773,7 +2860,7 @@ sub cpl {
     } elsif ($line =~ /^d\s/) {
        @return = cplx('CPAN::Distribution',$word);
     } elsif ($line =~ m/^(
-                          [mru]|make|clean|dump|test|install|readme|look|cvs_import
+                          [mru]|make|clean|dump|get|test|install|readme|look|cvs_import
                          )\s/x ) {
         if ($word =~ /^Bundle::/) {
             CPAN::Shell->local_bundles;
@@ -2850,7 +2937,7 @@ package CPAN::Index;
 #-> sub CPAN::Index::force_reload ;
 sub force_reload {
     my($class) = @_;
-    $CPAN::Index::last_time = 0;
+    $CPAN::Index::LAST_TIME = 0;
     $class->reload(1);
 }
 
@@ -2875,9 +2962,9 @@ sub reload {
     }
     if ( $CPAN::META->{PROTOCOL} < PROTOCOL  ) {
         # warn "Setting last_time to 0";
-        $last_time = 0; # No warning necessary
+        $LAST_TIME = 0; # No warning necessary
     }
-    return if $last_time + $CPAN::Config->{index_expire}*86400 > $time
+    return if $LAST_TIME + $CPAN::Config->{index_expire}*86400 > $time
        and ! $force;
     if (0) {
         # IFF we are developing, it helps to wipe out the memory
@@ -2887,7 +2974,7 @@ sub reload {
     }
     {
         my($debug,$t2);
-        local $last_time = $time;
+        local $LAST_TIME = $time;
         local $CPAN::META->{PROTOCOL} = PROTOCOL;
 
         my $needshort = $^O eq "dos";
@@ -2927,7 +3014,7 @@ sub reload {
         $time = $t2;
         CPAN->debug($debug) if $CPAN::DEBUG;
     }
-    $last_time = $time;
+    $LAST_TIME = $time;
     $CPAN::META->{PROTOCOL} = PROTOCOL;
 }
 
@@ -2999,12 +3086,12 @@ sub rd_modpacks {
        push @lines, @ls;
     }
     # read header
-    my $line_count;
+    my($line_count,$last_updated);
     while (@lines) {
        my $shift = shift(@lines);
-       $shift =~ /^Line-Count:\s+(\d+)/;
-       $line_count = $1 if $1;
        last if $shift =~ /^\s*$/;
+       $shift =~ /^Line-Count:\s+(\d+)/ and $line_count = $1;
+        $shift =~ /^Last-Updated:\s+(.+)/ and $last_updated = $1;
     }
     if (not defined $line_count) {
 
@@ -3024,6 +3111,41 @@ CPAN mirror. I'll continue but problems seem likely to happen.\a\n},
 $index_target, $line_count, scalar(@lines);
 
     }
+    if (not defined $last_updated) {
+
+       warn qq{Warning: Your $index_target does not contain a Last-Updated header.
+Please check the validity of the index file by comparing it to more
+than one CPAN mirror. I'll continue but problems seem likely to
+happen.\a
+};
+
+       sleep 5;
+    } else {
+
+       $CPAN::Frontend
+            ->myprint(sprintf qq{  Database was generated on %s\n},
+                      $last_updated);
+        $DATE_OF_02 = $last_updated;
+
+        if ($CPAN::META->has_inst(HTTP::Date)) {
+            require HTTP::Date;
+            my($age) = (time - HTTP::Date::str2time($last_updated))/3600/24;
+            if ($age > 30) {
+
+                $CPAN::Frontend
+                    ->mywarn(sprintf
+                             qq{Warning: This index file is %d days old.
+  Please check the host you chose as your CPAN mirror for staleness.
+  I'll continue but problems seem likely to happen.\a\n},
+                             $age);
+
+            }
+        } else {
+            $CPAN::Frontend->myprint("  HTTP::Date not available\n");
+        }
+    }
+
+
     # A necessity since we have metadata_cache: delete what isn't
     # there anymore
     my $secondtime = $CPAN::META->exists("CPAN::Module","CPAN");
@@ -3145,8 +3267,8 @@ sub rd_modlist {
     while (@eval) {
        my $shift = shift(@eval);
        if ($shift =~ /^Date:\s+(.*)/){
-           return if $date_of_03 eq $1;
-           ($date_of_03) = $1;
+           return if $DATE_OF_03 eq $1;
+           ($DATE_OF_03) = $1;
        }
        last if $shift =~ /^\s*$/;
     }
@@ -3177,7 +3299,8 @@ sub write_metadata_cache {
        $cache->{$k} = $CPAN::META->{readonly}{$k}; # unsafe meta access, ok
     }
     my $metadata_file = MM->catfile($CPAN::Config->{cpan_home},"Metadata");
-    $cache->{last_time} = $last_time;
+    $cache->{last_time} = $LAST_TIME;
+    $cache->{DATE_OF_02} = $DATE_OF_02;
     $cache->{PROTOCOL} = PROTOCOL;
     $CPAN::Frontend->myprint("Going to write $metadata_file\n");
     eval { Storable::nstore($cache, $metadata_file) };
@@ -3196,7 +3319,7 @@ sub read_metadata_cache {
     eval { $cache = Storable::retrieve($metadata_file) };
     $CPAN::Frontend->mywarn($@) if $@;
     if (!$cache || ref $cache ne 'HASH'){
-        $last_time = 0;
+        $LAST_TIME = 0;
         return;
     }
     if (exists $cache->{PROTOCOL}) {
@@ -3237,14 +3360,18 @@ sub read_metadata_cache {
     $CPAN::META->{PROTOCOL} ||=
         $cache->{PROTOCOL}; # reading does not up or downgrade, but it
                             # does initialize to some protocol
-    $last_time = $cache->{last_time};
+    $LAST_TIME = $cache->{last_time};
+    $DATE_OF_02 = $cache->{DATE_OF_02};
+    $CPAN::Frontend->myprint("  Database was generated on $DATE_OF_02\n")
+       if defined $DATE_OF_02; # An old cache may not contain DATE_OF_02
+    return;
 }
 
 package CPAN::InfoObj;
 
 # Accessors
 sub cpan_userid { shift->{RO}{CPAN_USERID} }
-sub id { shift->{ID} }
+sub id { shift->{ID}; }
 
 #-> sub CPAN::InfoObj::new ;
 sub new {
@@ -3352,13 +3479,25 @@ sub dump {
 
 package CPAN::Author;
 
+#-> sub CPAN::Author::id
+sub id {
+    my $self = shift;
+    my $id = $self->{ID};
+    $CPAN::Frontend->mydie("Illegal author id[$id]") unless $id =~ /^[A-Z]/;
+    $id;
+}
+
 #-> sub CPAN::Author::as_glimpse ;
 sub as_glimpse {
     my($self) = @_;
     my(@m);
     my $class = ref($self);
     $class =~ s/^CPAN:://;
-    push @m, sprintf "%-15s %s (%s)\n", $class, $self->{ID}, $self->fullname;
+    push @m, sprintf(qq{%-15s %s ("%s" <%s>)\n},
+                     $class,
+                     $self->{ID},
+                     $self->fullname,
+                     $self->email);
     join "", @m;
 }
 
@@ -3377,31 +3516,49 @@ sub ls {
     my $id = $self->id;
 
     # adapted from CPAN::Distribution::verifyMD5 ;
-    my(@chksumfile);
-    @chksumfile = $self->id =~ /(.)(.)(.*)/;
-    $chksumfile[1] = join "", @chksumfile[0,1];
-    $chksumfile[2] = join "", @chksumfile[1,2];
-    push @chksumfile, "CHECKSUMS";
-    print join "", map {
+    my(@csf); # chksumfile
+    @csf = $self->id =~ /(.)(.)(.*)/;
+    $csf[1] = join "", @csf[0,1];
+    $csf[2] = join "", @csf[1,2];
+    my(@dl);
+    @dl = $self->dir_listing([$csf[0],"CHECKSUMS"], 0);
+    unless (grep {$_->[2] eq $csf[1]} @dl) {
+        $CPAN::Frontend->myprint("No files in the directory of $id\n");
+        return;
+    }
+    @dl = $self->dir_listing([@csf[0,1],"CHECKSUMS"], 0);
+    unless (grep {$_->[2] eq $csf[2]} @dl) {
+        $CPAN::Frontend->myprint("No files in the directory of $id\n");
+        return;
+    }
+    @dl = $self->dir_listing([@csf,"CHECKSUMS"], 1);
+    $CPAN::Frontend->myprint(join "", map {
         sprintf("%8d %10s %s/%s\n", $_->[0], $_->[1], $id, $_->[2])
-    } sort { $a->[2] cmp $b->[2] } $self->dir_listing(\@chksumfile);
+    } sort { $a->[2] cmp $b->[2] } @dl);
 }
 
+# returns an array of arrays, the latter contain (size,mtime,filename)
 #-> sub CPAN::Author::dir_listing ;
 sub dir_listing {
     my $self = shift;
     my $chksumfile = shift;
+    my $recursive = shift;
     my $lc_want =
        MM->catfile($CPAN::Config->{keep_source_where},
                     "authors", "id", @$chksumfile);
     local($") = "/";
+    # connect "force" argument with "index_expire".
+    my $force = 0;
+    if (my @stat = stat $lc_want) {
+        $force = $stat[9] + $CPAN::Config->{index_expire}*86400 <= time;
+    }
     my $lc_file = CPAN::FTP->localize("authors/id/@$chksumfile",
-                                      $lc_want,1);
+                                      $lc_want,$force);
     unless ($lc_file) {
         $CPAN::Frontend->myprint("Trying $lc_want.gz\n");
        $chksumfile->[-1] .= ".gz";
        $lc_file = CPAN::FTP->localize("authors/id/@$chksumfile",
-                                      "$lc_want.gz",1);
+                                       "$lc_want.gz",1);
        if ($lc_file) {
            $lc_file =~ s{\.gz(?!\n)\Z}{}; #};
            CPAN::Tarzip->gunzip("$lc_file.gz",$lc_file);
@@ -3430,12 +3587,16 @@ sub dir_listing {
     my(@result,$f);
     for $f (sort keys %$cksum) {
         if (exists $cksum->{$f}{isdir}) {
-            my(@dir) = @$chksumfile;
-            pop @dir;
-            push @dir, $f, "CHECKSUMS";
-            push @result, map {
-                [$_->[0], $_->[1], "$f/$_->[2]"]
-            } $self->dir_listing(\@dir);
+            if ($recursive) {
+                my(@dir) = @$chksumfile;
+                pop @dir;
+                push @dir, $f, "CHECKSUMS";
+                push @result, map {
+                    [$_->[0], $_->[1], "$f/$_->[2]"]
+                } $self->dir_listing(\@dir,1);
+            } else {
+                push @result, [ 0, "-", $f ];
+            }
         } else {
             push @result, [
                            ($cksum->{$f}{"size"}||0),
@@ -3461,8 +3622,12 @@ sub undelay {
 sub normalize {
     my($self,$s) = @_;
     $s = $self->id unless defined $s;
-    if ($s =~ tr|/|| == 1) {
-        return $s if $s =~ m|^N/A|;
+    if (
+        $s =~ tr|/|| == 1
+        or
+        $s !~ m|[A-Z]/[A-Z-]{2}/[A-Z-]{2,}/|
+       ) {
+        return $s if $s =~ m:^N/A|^Contact Author: ;
         $s =~ s|^(.)(.)([^/]*/)(.+)$|$1/$1$2/$1$2$3$4| or
             $CPAN::Frontend->mywarn("Strange distribution name [$s]");
         CPAN->debug("s[$s]") if $CPAN::DEBUG;
@@ -3540,7 +3705,7 @@ sub called_for {
     return $self->{CALLED_FOR};
 }
 
-#-> sub CPAN::Distribution::my_chdir ;
+#-> sub CPAN::Distribution::safe_chdir ;
 sub safe_chdir {
     my($self,$todir) = @_;
     # we die if we cannot chdir and we are debuggable
@@ -3581,9 +3746,16 @@ sub get {
                    );
 
     $self->debug("Doing localize") if $CPAN::DEBUG;
-    $local_file =
-       CPAN::FTP->localize("authors/id/$self->{ID}", $local_wanted)
-              or $CPAN::Frontend->mydie("Giving up on '$local_wanted'\n");
+    unless ($local_file =
+            CPAN::FTP->localize("authors/id/$self->{ID}",
+                                $local_wanted)) {
+        my $note = "";
+        if ($CPAN::Index::DATE_OF_02) {
+            $note = "Note: Current database in memory was generated ".
+                "on $CPAN::Index::DATE_OF_02\n";
+        }
+        $CPAN::Frontend->mydie("Giving up on '$local_wanted'\n$note");
+    }
     $self->debug("local_file[$local_file]") if $CPAN::DEBUG;
     $self->{localfile} = $local_file;
     return if $CPAN::Signal;
@@ -3591,11 +3763,11 @@ sub get {
     #
     # Check integrity
     #
-    if ($CPAN::META->has_inst("MD5")) {
-       $self->debug("MD5 is installed, verifying");
+    if ($CPAN::META->has_inst("Digest::MD5")) {
+       $self->debug("Digest::MD5 is installed, verifying");
        $self->verifyMD5;
     } else {
-       $self->debug("MD5 is NOT installed");
+       $self->debug("Digest::MD5 is NOT installed");
     }
     return if $CPAN::Signal;
 
@@ -3684,12 +3856,14 @@ sub get {
     my($mpl) = MM->catfile($packagedir,"Makefile.PL");
     my($mpl_exists) = -f $mpl;
     unless ($mpl_exists) {
-        # Steffen's stupid NFS has problems to see an existing
-        # Makefile.PL such a short time after the directory was
-        # renamed. Maybe this trick helps
-        $dh = DirHandle->new($packagedir)
+        # NFS has been reported to have racing problems after the
+        # renaming of a directory in some environments.
+        # This trick helps.
+        sleep 1;
+        my $mpldh = DirHandle->new($packagedir)
             or Carp::croak("Couldn't opendir $packagedir: $!");
-        $mpl_exists = grep /^Makefile\.PL$/, $dh->read;
+        $mpl_exists = grep /^Makefile\.PL$/, $mpldh->read;
+        $mpldh->close;
     }
     unless ($mpl_exists) {
         $self->debug(sprintf("makefilepl[%s]anycwd[%s]",
@@ -3792,7 +3966,7 @@ sub look {
     my($self) = @_;
 
     if ($^O eq 'MacOS') {
-      $self->ExtUtils::MM_MacOS::look;
+      $self->Mac::BuildTools::look;
       return;
     }
 
@@ -3808,14 +3982,22 @@ Please define it with "o conf shell <your shell>"
        return;
     }
     my $dist = $self->id;
-    my $dir  = $self->dir or $self->get;
-    $dir = $self->dir;
+    my $dir;
+    unless ($dir = $self->dir) {
+        $self->get;
+    }
+    unless ($dir ||= $self->dir) {
+       $CPAN::Frontend->mywarn(qq{
+Could not determine which directory to use for looking at $dist.
+});
+       return;
+    }
     my $pwd  = CPAN::anycwd();
-    chdir($dir) or $CPAN::Frontend->mydie(qq{Could not chdir to "$dir": $!});
+    $self->safe_chdir($dir);
     $CPAN::Frontend->myprint(qq{Working directory is $dir\n});
     system($CPAN::Config->{'shell'}) == 0
        or $CPAN::Frontend->mydie("Subprocess shell error");
-    chdir($pwd) or $CPAN::Frontend->mydie(qq{Could not chdir to "$pwd": $!});
+    $self->safe_chdir($pwd);
 }
 
 # CPAN::Distribution::cvs_import ;
@@ -3875,7 +4057,7 @@ sub readme {
        or $CPAN::Frontend->mydie(qq{No $sans.readme found});;
 
     if ($^O eq 'MacOS') {
-        ExtUtils::MM_MacOS::launch_file($local_file);
+        Mac::BuildTools::launch_file($local_file);
         return;
     }
 
@@ -3971,7 +4153,7 @@ sub MD5_check_file {
        unless ($eq) {
          # had to inline it, when I tied it, the tiedness got lost on
          # the call to eq_MD5. (Jan 1998)
-         my $md5 = MD5->new;
+         my $md5 = Digest::MD5->new;
          my($data,$ref);
          $ref = \$data;
          while ($fh->READ($ref, 4096) > 0){
@@ -4030,7 +4212,7 @@ going awry right now.
 #-> sub CPAN::Distribution::eq_MD5 ;
 sub eq_MD5 {
     my($self,$fh,$expectMD5) = @_;
-    my $md5 = MD5->new;
+    my $md5 = Digest::MD5->new;
     my($data);
     while (read($fh, $data, 4096)){
       $md5->add($data);
@@ -4177,7 +4359,7 @@ or
     $self->debug("Changed directory to $builddir") if $CPAN::DEBUG;
 
     if ($^O eq 'MacOS') {
-        ExtUtils::MM_MacOS::make($self);
+        Mac::BuildTools::make($self);
         return;
     }
 
@@ -4238,7 +4420,7 @@ or
        } else {
          $self->{writemakefile} =
              qq{NO Makefile.PL refused to write a Makefile.};
-         # It's probably worth to record the reason, so let's retry
+         # It's probably worth it to record the reason, so let's retry
          # local $/;
          # my $fh = IO::File->new("$system |"); # STDERR? STDIN?
          # $self->{writemakefile} .= <$fh>;
@@ -4423,7 +4605,7 @@ sub test {
        if $CPAN::DEBUG;
 
     if ($^O eq 'MacOS') {
-        ExtUtils::MM_MacOS::make_test($self);
+        Mac::BuildTools::make_test($self);
         return;
     }
 
@@ -4454,7 +4636,7 @@ sub clean {
     $self->debug("Changed directory to $self->{'build_dir'}") if $CPAN::DEBUG;
 
     if ($^O eq 'MacOS') {
-        ExtUtils::MM_MacOS::make_clean($self);
+        Mac::BuildTools::make_clean($self);
         return;
     }
 
@@ -4529,7 +4711,7 @@ sub install {
        if $CPAN::DEBUG;
 
     if ($^O eq 'MacOS') {
-        ExtUtils::MM_MacOS::make_install($self);
+        Mac::BuildTools::make_install($self);
         return;
     }
 
@@ -4613,59 +4795,65 @@ sub as_string {
 
 #-> sub CPAN::Bundle::contains ;
 sub contains {
-  my($self) = @_;
-  my($parsefile) = $self->inst_file || "";
-  my($id) = $self->id;
-  $self->debug("parsefile[$parsefile]id[$id]") if $CPAN::DEBUG;
-  unless ($parsefile) {
-    # Try to get at it in the cpan directory
-    $self->debug("no parsefile") if $CPAN::DEBUG;
-    Carp::confess "I don't know a $id" unless $self->cpan_file;
-    my $dist = $CPAN::META->instance('CPAN::Distribution',
-                                    $self->cpan_file);
-    $dist->get;
-    $self->debug($dist->as_string) if $CPAN::DEBUG;
-    my($todir) = $CPAN::Config->{'cpan_home'};
-    my(@me,$from,$to,$me);
-    @me = split /::/, $self->id;
-    $me[-1] .= ".pm";
-    $me = MM->catfile(@me);
-    $from = $self->find_bundle_file($dist->{'build_dir'},$me);
-    $to = MM->catfile($todir,$me);
-    File::Path::mkpath(File::Basename::dirname($to));
-    File::Copy::copy($from, $to)
-       or Carp::confess("Couldn't copy $from to $to: $!");
-    $parsefile = $to;
-  }
-  my @result;
-  my $fh = FileHandle->new;
-  local $/ = "\n";
-  open($fh,$parsefile) or die "Could not open '$parsefile': $!";
-  my $in_cont = 0;
-  $self->debug("parsefile[$parsefile]") if $CPAN::DEBUG;
-  while (<$fh>) {
-    $in_cont = m/^=(?!head1\s+CONTENTS)/ ? 0 :
-       m/^=head1\s+CONTENTS/ ? 1 : $in_cont;
-    next unless $in_cont;
-    next if /^=/;
-    s/\#.*//;
-    next if /^\s+$/;
-    chomp;
-    push @result, (split " ", $_, 2)[0];
-  }
-  close $fh;
-  delete $self->{STATUS};
-  $self->{CONTAINS} = \@result;
-  $self->debug("CONTAINS[@result]") if $CPAN::DEBUG;
-  unless (@result) {
-    $CPAN::Frontend->mywarn(qq{
-The bundle file "$parsefile" may be a broken
+    my($self) = @_;
+    my($inst_file) = $self->inst_file || "";
+    my($id) = $self->id;
+    $self->debug("inst_file[$inst_file]id[$id]") if $CPAN::DEBUG;
+    unless ($inst_file) {
+        # Try to get at it in the cpan directory
+        $self->debug("no inst_file") if $CPAN::DEBUG;
+        my $cpan_file;
+        $CPAN::Frontend->mydie("I don't know a bundle with ID $id\n") unless
+              $cpan_file = $self->cpan_file;
+        if ($cpan_file eq "N/A") {
+            $CPAN::Frontend->mydie("Bundle $id not found on disk and not on CPAN.
+  Maybe stale symlink? Maybe removed during session? Giving up.\n");
+        }
+        my $dist = $CPAN::META->instance('CPAN::Distribution',
+                                         $self->cpan_file);
+        $dist->get;
+        $self->debug($dist->as_string) if $CPAN::DEBUG;
+        my($todir) = $CPAN::Config->{'cpan_home'};
+        my(@me,$from,$to,$me);
+        @me = split /::/, $self->id;
+        $me[-1] .= ".pm";
+        $me = MM->catfile(@me);
+        $from = $self->find_bundle_file($dist->{'build_dir'},$me);
+        $to = MM->catfile($todir,$me);
+        File::Path::mkpath(File::Basename::dirname($to));
+        File::Copy::copy($from, $to)
+              or Carp::confess("Couldn't copy $from to $to: $!");
+        $inst_file = $to;
+    }
+    my @result;
+    my $fh = FileHandle->new;
+    local $/ = "\n";
+    open($fh,$inst_file) or die "Could not open '$inst_file': $!";
+    my $in_cont = 0;
+    $self->debug("inst_file[$inst_file]") if $CPAN::DEBUG;
+    while (<$fh>) {
+        $in_cont = m/^=(?!head1\s+CONTENTS)/ ? 0 :
+            m/^=head1\s+CONTENTS/ ? 1 : $in_cont;
+        next unless $in_cont;
+        next if /^=/;
+        s/\#.*//;
+        next if /^\s+$/;
+        chomp;
+        push @result, (split " ", $_, 2)[0];
+    }
+    close $fh;
+    delete $self->{STATUS};
+    $self->{CONTAINS} = \@result;
+    $self->debug("CONTAINS[@result]") if $CPAN::DEBUG;
+    unless (@result) {
+        $CPAN::Frontend->mywarn(qq{
+The bundle file "$inst_file" may be a broken
 bundlefile. It seems not to contain any bundle definition.
 Please check the file and if it is bogus, please delete it.
 Sorry for the inconvenience.
 });
-  }
-  @result;
+    }
+    @result;
 }
 
 #-> sub CPAN::Bundle::find_bundle_file
@@ -4689,7 +4877,7 @@ sub find_bundle_file {
     my $what2 = $what;
     if ($^O eq 'MacOS') {
       $what =~ s/^://;
-      $what2 =~ tr|:|/|;
+      $what =~ tr|:|/|;
       $what2 =~ s/:Bundle://;
       $what2 =~ tr|:|/|;
     } else {
@@ -5017,8 +5205,11 @@ sub as_string {
             # warn "dist[$dist]";
             # mff=manifest file; mfh=manifest handle
             my($mff,$mfh);
-            if ($dist->{build_dir} and
-                -f ($mff = MM->catfile($dist->{build_dir}, "MANIFEST")) and
+            if (
+                $dist->{build_dir}
+                and
+                (-f  ($mff = MM->catfile($dist->{build_dir}, "MANIFEST")))
+                and
                 $mfh = FileHandle->new($mff)
                ) {
                 CPAN->debug("mff[$mff]") if $CPAN::DEBUG;
@@ -5091,7 +5282,8 @@ sub manpage_headline {
 }
 
 #-> sub CPAN::Module::cpan_file ;
-sub cpan_file    {
+# Note: also inherited by CPAN::Bundle
+sub cpan_file {
     my $self = shift;
     CPAN->debug(sprintf "id[%s]", $self->id) if $CPAN::DEBUG;
     unless (defined $self->{RO}{CPAN_FILE}) {
@@ -5532,7 +5724,7 @@ is available. Can\'t continue.
         $tar->extract(@af);
     }
 
-    ExtUtils::MM_MacOS::convert_files([$tar->list_files], 1)
+    Mac::BuildTools::convert_files([$tar->list_files], 1)
         if ($^O eq 'MacOS');
 
     return 1;
@@ -5797,6 +5989,13 @@ displays the README file of the associated distribution. C<Look> gets
 and untars (if not yet done) the distribution file, changes to the
 appropriate directory and opens a subshell process in that directory.
 
+=item ls author
+
+C<ls> lists all distribution files in and below an author's CPAN
+directory. Only those files that contain modules are listed and if
+there is more than one for any given module, only the most recent one
+is listed.
+
 =item Signals
 
 CPAN.pm installs signal handlers for SIGINT and SIGTERM. While you are
@@ -5867,10 +6066,11 @@ separated):
 Modules know their associated Distribution objects. They always refer
 to the most recent official release. Developers may mark their releases
 as unstable development versions (by inserting an underbar into the
-visible version number), so the really hottest and newest distribution
-file is not always the default.  If a module Foo circulates on CPAN in
-both version 1.23 and 1.23_90, CPAN.pm offers a convenient way to
-install version 1.23 by saying
+module version number which will also be reflected in the distribution
+name when you run 'make dist'), so the really hottest and newest 
+distribution is not always the default.  If a module Foo circulates 
+on CPAN in both version 1.23 and 1.23_90, CPAN.pm offers a convenient 
+way to install version 1.23 by saying
 
     install Foo
 
@@ -5924,7 +6124,7 @@ functionalities that are available in the shell.
     perl -MCPAN -e 'CPAN::Shell->install(CPAN::Shell->r)'
 
     # install my favorite programs if necessary:
-    for $mod (qw(Net::FTP MD5 Data::Dumper)){
+    for $mod (qw(Net::FTP Digest::MD5 Data::Dumper)){
         my $obj = CPAN::Shell->expand('Module',$mod);
         $obj->install;
     }
@@ -5985,7 +6185,7 @@ beta and partially even alpha. In the following paragraphs only those
 methods are documented that have proven useful over a longer time and
 thus are unlikely to change.
 
-=over
+=over 4
 
 =item CPAN::Author::as_glimpse()
 
@@ -6420,6 +6620,8 @@ defined:
   prerequisites_policy
                      what to do if you are missing module prerequisites
                      ('follow' automatically, 'ask' me, or 'ignore')
+  proxy_user         username for accessing an authenticating proxy
+  proxy_pass         password for accessing an authenticating proxy
   scan_cache        controls scanning of cache ('atstart' or 'never')
   tar                location of external program tar
   term_is_latin      if true internal UTF-8 is translated to ISO-8859-1
@@ -6548,7 +6750,7 @@ that you can configure ncftp so that it works for your firewall.
 
 Firewalls can be categorized into three basic types.
 
-=over
+=over 4
 
 =item http firewall
 
@@ -6582,7 +6784,7 @@ FTP connections need to be done in a passive mode.
 
 There are two that I can think off.
 
-=over
+=over 4
 
 =item SOCKS
 
@@ -6621,7 +6823,7 @@ Your milage may vary...
 
 =head1 FAQ
 
-=over
+=over 4
 
 =item 1)
 
@@ -6766,7 +6968,7 @@ becomes stable with regard to charset issues.
 
 We should give coverage for B<all> of the CPAN and not just the PAUSE
 part, right? In this discussion CPAN and PAUSE have become equal --
-but they are not. PAUSE is authors/, modules/ and scripts/. CPAN is 
+but they are not. PAUSE is authors/, modules/ and scripts/. CPAN is
 PAUSE plus the clpa/, doc/, misc/, ports/, and src/.
 
 Future development should be directed towards a better integration of
@@ -6781,6 +6983,11 @@ traditional method of building a Perl module package from a shell.
 
 Andreas Koenig E<lt>andreas.koenig@anima.deE<gt>
 
+=head1 TRANSLATIONS
+
+Kawai,Takanori provides a Japanese translation of this manpage at
+http://member.nifty.ne.jp/hippo2000/perltips/CPAN.htm
+
 =head1 SEE ALSO
 
 perl(1), CPAN::Nox(3)