diff options
author | Edmund von der Burg <evdb@mysociety.org> | 2011-03-12 14:48:52 +0000 |
---|---|---|
committer | Edmund von der Burg <evdb@mysociety.org> | 2011-03-12 14:49:21 +0000 |
commit | 6b0007c775df32b72cf5f0e07041d2b9a2e3cd0d (patch) | |
tree | 7f5a014ffc8bfac2a1aa4ea7b5b50a34a129c8b5 /perl-external | |
parent | 1cd646d598eb226609d08d2ee7f51f21760f2eb1 (diff) |
Rename dir to be more descriptive
Diffstat (limited to 'perl-external')
-rw-r--r-- | perl-external/.gitignore | 1 | ||||
-rw-r--r-- | perl-external/bin/add_module.pl | 52 | ||||
-rw-r--r-- | perl-external/bin/build_all_modules.pl | 40 | ||||
-rw-r--r-- | perl-external/bin/cpanm | 6671 | ||||
-rw-r--r-- | perl-external/dpan_config | 2 | ||||
-rw-r--r-- | perl-external/dpan_l4p_config | 4 | ||||
-rw-r--r-- | perl-external/minicpan/.gitignore | 1 | ||||
-rw-r--r-- | perl-external/minicpan/extra_reports/common-sense-3.4.txt | 2 | ||||
-rw-r--r-- | perl-external/minicpan/modules/02packages.details.txt.gz | bin | 0 -> 306 bytes | |||
-rw-r--r-- | perl-external/minicpan/modules/03modlist.data.gz | bin | 0 -> 222 bytes | |||
-rw-r--r-- | perl-external/modules.txt | 0 | ||||
-rw-r--r-- | perl-external/urls.txt | 0 |
12 files changed, 6773 insertions, 0 deletions
diff --git a/perl-external/.gitignore b/perl-external/.gitignore new file mode 100644 index 000000000..46ff56b3e --- /dev/null +++ b/perl-external/.gitignore @@ -0,0 +1 @@ +distributions diff --git a/perl-external/bin/add_module.pl b/perl-external/bin/add_module.pl new file mode 100644 index 000000000..6c9be8df2 --- /dev/null +++ b/perl-external/bin/add_module.pl @@ -0,0 +1,52 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use IPC::Run3; +use LWP::Simple; +use File::Slurp; +use Path::Class; + +my $root_dir = file(__FILE__)->dir->parent->absolute->stringify; +my $module_list = "$root_dir/modules.txt"; +my $url_list = "$root_dir/urls.txt"; +my $minicpan = "$root_dir/minicpan"; +my $local_lib = "$root_dir/../local-lib5"; +my $cpanm_cmd = "perl $root_dir/bin/cpanm -l $local_lib --reinstall"; + +my $module = $ARGV[0] || die "Usage: $0 Dist::To::Add"; + +# try to install the distribution using cpanm +my $out = ''; +my $cmd = "$cpanm_cmd $module"; +print " running '$cmd'\n"; +run3( $cmd, undef, \$out, \$out ) + || die "Error running '$cmd'"; + +my @fetched_urls = + map { s{.*(http://\S+).*}{$1}; $_ } + grep { m{^Fetching http://search.cpan.org} } + split /\n/, $out; + +write_file( $module_list, { append => 1 }, "$module\n" ); +write_file( $url_list, { append => 1 }, map { "$_\n" } @fetched_urls ); + +foreach my $url ( read_file($url_list) ) { + + my ($filename) = $url =~ m{/(authors/.+)$}; + + my $destination = file("$minicpan/$filename"); + $destination->dir->mkpath; + + next if -e $destination; + + print " $url\n -> $destination\n"; + + is_success( getstore( $url, "$destination" ) ) + || die "Error saving $url to $destination"; +} + +# go to the minicpan dir and run dpan there +chdir $minicpan; +exec 'dpan -f ../dpan_config'; diff --git a/perl-external/bin/build_all_modules.pl b/perl-external/bin/build_all_modules.pl new file mode 100644 index 000000000..81f3d2e63 --- /dev/null +++ b/perl-external/bin/build_all_modules.pl @@ -0,0 +1,40 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use IPC::Run3; +use LWP::Simple; +use YAML; +use File::Slurp; +use Path::Class; + +my $root_dir = file(__FILE__)->dir->parent->absolute->stringify; +my $module_list = "$root_dir/modules.txt"; +my $minicpan = "$root_dir/minicpan"; +my $local_lib = "$root_dir/../local-lib5"; +my $cpanm_cmd = + "perl $root_dir/bin/cpanm --mirror $minicpan --mirror-only -l $local_lib"; + +my @modules = map { s{\s+$}{}; $_; } read_file($module_list); + +foreach my $module (@modules) { + print " --- installing $module ---\n"; + + my $out = ''; + my $cmd = "$cpanm_cmd $module"; + + print " running '$cmd'\n"; + + run3( $cmd, undef, \$out, \$out ) + || die "Error running '$cmd'"; + + my @lines = + grep { m{\S} } + split /\n+/, $out; + my $last_line = $lines[-1]; + + die "Error building '$module':\n\n$out\n\n" + unless $last_line =~ m{Successfully installed } + || $last_line =~ m{is up to date}; +} diff --git a/perl-external/bin/cpanm b/perl-external/bin/cpanm new file mode 100644 index 000000000..1c552fac2 --- /dev/null +++ b/perl-external/bin/cpanm @@ -0,0 +1,6671 @@ +#!/usr/bin/env perl +# +# You want to install cpanminus? Run the following command and it will +# install itself for you. You might want to run it as a root with sudo +# if you want to install to places like /usr/local/bin. +# +# % curl -L http://cpanmin.us | perl - --self-upgrade +# +# If you don't have curl but wget, replace `curl -L` with `wget -O -`. +# +# For more details about this program, visit http://search.cpan.org/dist/App-cpanminus +# +# DO NOT EDIT -- this is an auto generated file +# This chunk of stuff was generated by App::FatPacker. To find the original +# file's code, look for the end of this BEGIN block or the string 'FATPACK' +BEGIN { +my %fatpacked; + +$fatpacked{"App/cpanminus.pm"} = <<'APP_CPANMINUS'; + package App::cpanminus; + our $VERSION = "1.3001"; + + =head1 NAME + + App::cpanminus - get, unpack, build and install modules from CPAN + + =head1 SYNOPSIS + + cpanm Module + + Run C<cpanm -h> for more options. + + =head1 DESCRIPTION + + cpanminus is a script to get, unpack, build and install modules from + CPAN and does nothing else. + + It's dependency free (can bootstrap itself), requires zero + configuration, and stands alone. When running, it requires only 10MB + of RAM. + + =head1 INSTALLATION + + There are several ways to install cpanminus to your system. + + =head2 Package management system + + There are Debian packages, RPMs, FreeBSD ports, and packages for other + operation systems available. If you want to use the package management system, + search for cpanminus and use the appropriate command to install. This makes it + easy to install C<cpanm> to your system without thinking about where to + install, and later upgrade. + + =head2 Installing to system perl + + You can also use the latest cpanminus to install cpanminus itself: + + curl -L http://cpanmin.us | perl - --sudo App::cpanminus + + This will install C<cpanm> to your bin directory like + C</usr/local/bin> (unless you configured C<INSTALL_BASE> with + L<local::lib>), so you probably need the C<--sudo> option. + + =head2 Installing to local perl (perlbrew) + + If you have perl in your home directory, which is the case if you use + tools like L<perlbrew>, you don't need the C<--sudo> option, since + you're most likely to have a write permission to the perl's library + path. You can just do: + + curl -L http://cpanmin.us | perl - App::cpanminus + + to install the C<cpanm> executable to the perl's bin path, like + C<~/perl5/perlbrew/bin/cpanm>. + + =head2 Downloaing the standalone executable + + You can also copy the standalone executable to whatever location you'd like. + + cd ~/bin + curl -LO http://xrl.us/cpanm + chmod +x cpanm + # edit shebang if you don't have /usr/bin/env + + This just works, but be sure to grab the new version manually when you + upgrade because C<--self-upgrade> might not work for this. + + =head1 DEPENDENCIES + + perl 5.8 or later. + + =over 4 + + =item * + + 'tar' executable (bsdtar or GNU tar version 1.22 are rcommended) or Archive::Tar to unpack files. + + =item * + + C compiler, if you want to build XS modules. + + =item * + + make + + =item * + + Module::Build (core in 5.10) + + =back + + =head1 QUESTIONS + + =head2 Another CPAN installer? + + OK, the first motivation was this: the CPAN shell runs out of memory (or swaps + heavily and gets really slow) on Slicehost/linode's most affordable plan with + only 256MB RAM. Should I pay more to install perl modules from CPAN? I don't + think so. + + =head2 But why a new client? + + First of all, let me be clear that CPAN and CPANPLUS are great tools + I've used for I<literally> years (you know how many modules I have on + CPAN, right?). I really respect their efforts of maintaining the most + important tools in the CPAN toolchain ecosystem. + + However, for less experienced users (mostly from outside the Perl community), + or even really experienced Perl developers who know how to shoot themselves in + their feet, setting up the CPAN toolchain often feels like yak shaving, + especially when all they want to do is just install some modules and start + writing code. + + =head2 Zero-conf? How does this module get/parse/update the CPAN index? + + It queries the CPAN Meta DB site running on Google AppEngine at + L<http://cpanmetadb.appspot.com/>. The site is updated every hour to reflect + the latest changes from fast syncing mirrors. The script then also falls back + to scrape the site L<http://search.cpan.org/>. + + Fetched files are unpacked in C<~/.cpanm> and automatically cleaned up + periodically. You can configure the location of this with the + C<PERL_CPANM_HOME> environment variable. + + =head2 Where does this install modules to? Do I need root access? + + It installs to wherever ExtUtils::MakeMaker and Module::Build are + configured to (via C<PERL_MM_OPT> and C<PERL_MB_OPT>). So if you're + using local::lib, then it installs to your local perl5 + directory. Otherwise it installs to the site_perl directory that + belongs to your perl. + + cpanminus at a boot time checks whether you have configured + local::lib, or have the permission to install modules to the site_perl + directory. If neither, it automatically sets up local::lib compatible + installation path in a C<perl5> directory under your home + directory. To avoid this, run the script as the root user, with + C<--sudo> option or with C<--local-lib> option. + + =head2 cpanminus can't install the module XYZ. Is it a bug? + + It is more likely a problem with the distribution itself. cpanminus + doesn't support or is known to have issues with distributions like as + follows: + + =over 4 + + =item * + + Tests that require input from STDIN. + + =item * + + Tests that might fail when C<AUTOMATED_TESTING> is enabled. + + =item * + + Modules that have invalid numeric values as VERSION (such as C<1.1a>) + + =back + + These failures can be reported back to the author of the module so + that they can fix it accordingly, rather than me. + + =head2 Does cpanm support the feature XYZ of L<CPAN> and L<CPANPLUS>? + + Most likely not. Here are the things that cpanm doesn't do by + itself. And it's a feature - you got that from the name I<minus>, + right? + + If you need these features, use L<CPAN>, L<CPANPLUS> or the standalone + tools that are mentioned. + + =over 4 + + =item * + + Bundle:: module dependencies + + =item * + + CPAN testers reporting + + =item * + + Building RPM packages from CPAN modules + + =item * + + Listing the outdated modules that needs upgrading. See L<cpan-outdated> + + =item * + + Uninstalling modules. See L<pm-uninstall>. + + =item * + + Showing the changes of the modules you're about to upgrade. See L<cpan-listchanges> + + =item * + + Patching CPAN modules with distroprefs. + + =back + + See L<cpanm> or C<cpanm -h> to see what cpanminus I<can> do :) + + =head1 COPYRIGHT + + Copyright 2010- Tatsuhiko Miyagawa + + The standalone executable contains the following modules embedded. + + =over 4 + + =item L<CPAN::DistnameInfo> Copyright 2003 Graham Barr + + =item L<Parse::CPAN::Meta> Copyright 2006-2009 Adam Kennedy + + =item L<local::lib> Copyright 2007-2009 Matt S Trout + + =item L<HTTP::Tiny> Copyright 2011 Christian Hansen + + =item L<Module::Metadata> Copyright 2001-2006 Ken Williams. 2010 Matt S Trout + + =item L<version> Copyright 2004-2010 John Peacock + + =back + + =head1 LICENSE + + Same as Perl. + + =head1 CREDITS + + =head2 CONTRIBUTORS + + Patches and code improvements were contributed by: + + Goro Fuji, Kazuhiro Osawa, Tokuhiro Matsuno, Kenichi Ishigaki, Ian + Wells, Pedro Melo, Masayoshi Sekimura, Matt S Trout, squeeky, horus + and Ingy dot Net. + + =head2 ACKNOWLEDGEMENTS + + Bug reports, suggestions and feedbacks were sent by, or general + acknowledgement goes to: + + Jesse Vincent, David Golden, Andreas Koenig, Jos Boumans, Chris + Williams, Adam Kennedy, Audrey Tang, J. Shirley, Chris Prather, Jesse + Luehrs, Marcus Ramberg, Shawn M Moore, chocolateboy, Chirs Nehren, + Jonathan Rockway, Leon Brocard, Simon Elliott, Ricardo Signes, AEvar + Arnfjord Bjarmason, Eric Wilhelm, Florian Ragwitz and xaicron. + + =head1 COMMUNITY + + =over 4 + + =item L<http://github.com/miyagawa/cpanminus> - source code repository, issue tracker + + =item L<irc://irc.perl.org/#toolchain> - discussions about Perl toolchain. I'm there. + + =back + + =head1 NO WARRANTY + + This software is provided "as-is," without any express or implied + warranty. In no event shall the author be held liable for any damages + arising from the use of the software. + + =head1 SEE ALSO + + L<CPAN> L<CPANPLUS> L<pip> + + =cut + + 1; +APP_CPANMINUS + +$fatpacked{"App/cpanminus/script.pm"} = <<'APP_CPANMINUS_SCRIPT'; + package App::cpanminus::script; + use strict; + use Config; + use Cwd (); + use File::Basename (); + use File::Path (); + use File::Spec (); + use File::Copy (); + use Getopt::Long (); + use Parse::CPAN::Meta; + + use constant WIN32 => $^O eq 'MSWin32'; + use constant SUNOS => $^O eq 'solaris'; + + our $VERSION = "1.3001"; + + my $quote = WIN32 ? q/"/ : q/'/; + + sub new { + my $class = shift; + + bless { + home => "$ENV{HOME}/.cpanm", + cmd => 'install', + seen => {}, + notest => undef, + installdeps => undef, + force => undef, + sudo => undef, + make => undef, + verbose => undef, + quiet => undef, + interactive => undef, + log => undef, + mirrors => [], + mirror_only => undef, + perl => $^X, + argv => [], + local_lib => undef, + self_contained => undef, + prompt_timeout => 0, + prompt => undef, + configure_timeout => 60, + try_lwp => 1, + try_wget => 1, + try_curl => 1, + uninstall_shadows => ($] < 5.012), + skip_installed => 1, + auto_cleanup => 7, # days + @_, + }, $class; + } + + sub env { + my($self, $key) = @_; + $ENV{"PERL_CPANM_" . $key}; + } + + sub parse_options { + my $self = shift; + + local @ARGV = @{$self->{argv}}; + push @ARGV, split /\s+/, $self->env('OPT'); + push @ARGV, @_; + + if ($0 ne '-' && !-t STDIN){ # e.g. $ cpanm < author/requires.cpanm + push @ARGV, $self->load_argv_from_fh(\*STDIN); + } + + Getopt::Long::Configure("bundling"); + Getopt::Long::GetOptions( + 'f|force' => sub { $self->{skip_installed} = 0; $self->{force} = 1 }, + 'n|notest!' => \$self->{notest}, + 'S|sudo!' => \$self->{sudo}, + 'v|verbose' => sub { $self->{verbose} = $self->{interactive} = 1 }, + 'q|quiet' => \$self->{quiet}, + 'h|help' => sub { $self->{action} = 'show_help' }, + 'V|version' => sub { $self->{action} = 'show_version' }, + 'perl=s' => \$self->{perl}, + 'l|local-lib=s' => sub { $self->{local_lib} = $self->maybe_abs($_[1]) }, + 'L|local-lib-contained=s' => sub { $self->{local_lib} = $self->maybe_abs($_[1]); $self->{self_contained} = 1 }, + 'mirror=s@' => $self->{mirrors}, + 'mirror-only!' => \$self->{mirror_only}, + 'prompt!' => \$self->{prompt}, + 'installdeps' => \$self->{installdeps}, + 'skip-installed!' => \$self->{skip_installed}, + 'reinstall' => sub { $self->{skip_installed} = 0 }, + 'interactive!' => \$self->{interactive}, + 'i|install' => sub { $self->{cmd} = 'install' }, + 'info' => sub { $self->{cmd} = 'info' }, + 'look' => sub { $self->{cmd} = 'look'; $self->{skip_installed} = 0 }, + 'self-upgrade' => sub { $self->{cmd} = 'install'; $self->{skip_installed} = 1; push @ARGV, 'App::cpanminus' }, + 'uninst-shadows!' => \$self->{uninstall_shadows}, + 'lwp!' => \$self->{try_lwp}, + 'wget!' => \$self->{try_wget}, + 'curl!' => \$self->{try_curl}, + 'auto-cleanup=s' => \$self->{auto_cleanup}, + ); + + $self->{argv} = \@ARGV; + } + + sub check_libs { + my $self = shift; + return if $self->{_checked}++; + + $self->bootstrap_local_lib; + if (@{$self->{bootstrap_deps} || []}) { + local $self->{notest} = 1; # test failure in bootstrap should be tolerated + $self->install_deps(Cwd::cwd, 0, @{$self->{bootstrap_deps}}); + } + } + + sub doit { + my $self = shift; + + $self->setup_home; + $self->init_tools; + + if (my $action = $self->{action}) { + $self->$action() and return 1; + } + + $self->show_help(1) unless @{$self->{argv}}; + + $self->configure_mirrors; + + my @fail; + for my $module (@{$self->{argv}}) { + if ($module =~ s/\.pm$//i) { + my ($volume, $dirs, $file) = File::Spec->splitpath($module); + $module = join '::', grep { $_ } File::Spec->splitdir($dirs), $file; + } + $self->install_module($module, 0) + or push @fail, $module; + } + + if ($self->{base} && $self->{auto_cleanup}) { + $self->cleanup_workdirs; + } + + return !@fail; + } + + sub setup_home { + my $self = shift; + + $self->{home} = $self->env('HOME') if $self->env('HOME'); + + unless (_writable($self->{home})) { + die "Can't write to cpanm home '$self->{home}': You should fix it with chown/chmod first.\n"; + } + + $self->{base} = "$self->{home}/work/" . time . ".$$"; + File::Path::mkpath([ $self->{base} ], 0, 0777); + + my $link = "$self->{home}/latest-build"; + eval { unlink $link; symlink $self->{base}, $link }; + + $self->{log} = File::Spec->catfile($self->{home}, "build.log"); # because we use shell redirect + + { + my $log = $self->{log}; my $base = $self->{base}; + $self->{at_exit} = sub { + my $self = shift; + File::Copy::copy($self->{log}, "$self->{base}/build.log"); + }; + } + + open my $out, ">$self->{log}" or die "$self->{log}: $!"; + print $out "cpanm (App::cpanminus) $VERSION on perl $] built for $Config{archname}\n"; + print $out "Work directory is $self->{base}\n"; + } + + sub fetch_meta { + my($self, $dist) = @_; + + unless ($self->{mirror_only}) { + my $meta_yml = $self->get("http://cpansearch.perl.org/src/$dist->{cpanid}/$dist->{distvname}/META.yml"); + return $self->parse_meta_string($meta_yml); + } + + return undef; + } + + sub package_index_for { + my ($self, $mirror) = @_; + return $self->source_for($mirror) . "/02packages.details.txt"; + } + + sub generate_mirror_index { + my ($self, $mirror) = @_; + my $file = $self->package_index_for($mirror); + my $gz_file = $file . '.gz'; + my $index_mtime = (stat $gz_file)[9]; + + unless (-e $file && (stat $file)[9] >= $index_mtime) { + $self->chat("Uncompressing index file...\n"); + if (eval {require Compress::Zlib}) { + my $gz = Compress::Zlib::gzopen($gz_file, "rb") + or do { $self->diag_fail("$Compress::Zlib::gzerrno opening compressed index"); return}; + open my $fh, '>', $file + or do { $self->diag_fail("$! opening uncompressed index for write"); return }; + my $buffer; + while (my $status = $gz->gzread($buffer)) { + if ($status < 0) { + $self->diag_fail($gz->gzerror . " reading compressed index"); + return; + } + print $fh $buffer; + } + } else { + unless (system("gunzip -c $gz_file > $file")) { + $self->diag_fail("Cannot uncompress -- please install gunzip or Compress::Zlib"); + return; + } + } + utime $index_mtime, $index_mtime, $file; + } + return 1; + } + + sub search_mirror_index { + my ($self, $mirror, $module) = @_; + + open my $fh, '<', $self->package_index_for($mirror) or return; + while (<$fh>) { + if (m!^\Q$module\E\s+([\w\.]+)\s+(.*)!m) { + return $self->cpan_module($module, $2, $1); + } + } + + return; + } + + sub search_module { + my($self, $module) = @_; + + unless ($self->{mirror_only}) { + $self->chat("Searching $module on cpanmetadb ...\n"); + my $uri = "http://cpanmetadb.appspot.com/v1.0/package/$module"; + my $yaml = $self->get($uri); + my $meta = $self->parse_meta_string($yaml); + if ($meta->{distfile}) { + return $self->cpan_module($module, $meta->{distfile}, $meta->{version}); + } + + $self->diag_fail("Finding $module on cpanmetadb failed."); + + $self->chat("Searching $module on search.cpan.org ...\n"); + my $uri = "http://search.cpan.org/perldoc?$module"; + my $html = $self->get($uri); + $html =~ m!<a href="/CPAN/authors/id/(.*?\.(?:tar\.gz|tgz|tar\.bz2|zip))">! + and return $self->cpan_module($module, $1); + + $self->diag_fail("Finding $module on search.cpan.org failed."); + } + + MIRROR: for my $mirror (@{ $self->{mirrors} }) { + $self->chat("Searching $module on mirror $mirror ...\n"); + my $name = '02packages.details.txt.gz'; + my $uri = "$mirror/modules/$name"; + my $gz_file = $self->package_index_for($mirror) . '.gz'; + + unless ($self->{pkgs}{$uri}) { + $self->chat("Downloading index file $uri ...\n"); + $self->mirror($uri, $gz_file); + $self->generate_mirror_index($mirror) or next MIRROR; + $self->{pkgs}{$uri} = "!!retrieved!!"; + } + + my $pkg = $self->search_mirror_index($mirror, $module); + return $pkg if $pkg; + + $self->diag_fail("Finding $module on mirror $mirror failed."); + } + + return; + } + + sub source_for { + my($self, $mirror) = @_; + $mirror =~ s/[^\w\.\-]+/%/g; + + my $dir = "$self->{home}/sources/$mirror"; + File::Path::mkpath([ $dir ], 0, 0777); + + return $dir; + } + + sub load_argv_from_fh { + my($self, $fh) = @_; + + my @argv; + while(defined(my $line = <$fh>)){ + chomp $line; + $line =~ s/#.+$//; # comment + $line =~ s/^\s+//; # trim spaces + $line =~ s/\s+$//; # trim spaces + + push @argv, split ' ', $line if $line; + } + return @argv; + } + + sub show_version { + print "cpanm (App::cpanminus) version $VERSION\n"; + return 1; + } + + sub show_help { + my $self = shift; + + if ($_[0]) { + die <<USAGE; + Usage: cpanm [options] Module [...] + + Try `cpanm --help` or `man cpanm` for more options. + USAGE + } + + print <<HELP; + Usage: cpanm [options] Module [...] + + Options: + -v,--verbose Turns on chatty output + -q,--quiet Turns off all output + --interactive Turns on interactive configure (required for Task:: modules) + -f,--force force install + -n,--notest Do not run unit tests + -S,--sudo sudo to run install commands + --installdeps Only install dependencies + --reinstall Reinstall the distribution even if you already have the latest version installed + --mirror Specify the base URL for the mirror (e.g. http://cpan.cpantesters.org/) + --mirror-only Use the mirror's index file instead of the CPAN Meta DB + --prompt Prompt when configure/build/test fails + -l,--local-lib Specify the install base to install modules + -L,--local-lib-contained Specify the install base to install all non-core modules + --auto-cleanup Number of days that cpanm's work directories expire in. Defaults to 7 + + Commands: + --self-upgrade upgrades itself + --info Displays distribution info on CPAN + --look Opens the distribution with your SHELL + -V,--version Displays software version + + Examples: + + cpanm Test::More # install Test::More + cpanm MIYAGAWA/Plack-0.99_05.tar.gz # full distribution path + cpanm http://example.org/LDS/CGI.pm-3.20.tar.gz # install from URL + cpanm ~/dists/MyCompany-Enterprise-1.00.tar.gz # install from a local file + cpanm --interactive Task::Kensho # Configure interactively + cpanm . # install from local directory + cpanm --installdeps . # install all the deps for the current directory + cpanm -L extlib Plack # install Plack and all non-core deps into extlib + cpanm --mirror http://cpan.cpantesters.org/ DBI # use the fast-syncing mirror + + You can also specify the default options in PERL_CPANM_OPT environment variable in the shell rc: + + export PERL_CPANM_OPT="--prompt --reinstall -l ~/perl --mirror http://cpan.cpantesters.org" + + Type `man cpanm` or `perldoc cpanm` for the more detailed explanation of the options. + + HELP + + return 1; + } + + sub _writable { + my $dir = shift; + my @dir = File::Spec->splitdir($dir); + while (@dir) { + $dir = File::Spec->catdir(@dir); + if (-e $dir) { + return -w _; + } + pop @dir; + } + + return; + } + + sub maybe_abs { + my($self, $lib) = @_; + $lib =~ /^[~\/]/ ? $lib : Cwd::abs_path($lib); + } + + sub bootstrap_local_lib { + my $self = shift; + + # If -l is specified, use that. + if ($self->{local_lib}) { + return $self->setup_local_lib($self->{local_lib}); + } + + # root, locally-installed perl or --sudo: don't care about install_base + return if $self->{sudo} or (_writable($Config{installsitelib}) and _writable($Config{installsitebin})); + + # local::lib is configured in the shell -- yay + if ($ENV{PERL_MM_OPT} and ($ENV{MODULEBUILDRC} or $ENV{PERL_MB_OPT})) { + $self->bootstrap_local_lib_deps; + return; + } + + $self->setup_local_lib; + + $self->diag(<<DIAG); + ! + ! Can't write to $Config{installsitelib} and $Config{installsitebin}: Installing modules to $ENV{HOME}/perl5 + ! To turn off this warning, you have to do one of the following: + ! - run me as a root or with --sudo option (to install to $Config{installsitelib} and $Config{installsitebin}) + | - run me with --local-lib option e.g. cpanm --local-lib=~/perl5 + ! - Set PERL_CPANM_OPT="--local-lib=~/perl5" environment variable (in your shell rc file) + ! - Configure local::lib in your shell to set PERL_MM_OPT etc. + ! + DIAG + sleep 2; + } + + sub _core_only_inc { + my($self, $base) = @_; + require local::lib; + ( + local::lib->install_base_perl_path($base), + local::lib->install_base_arch_path($base), + @Config{qw(privlibexp archlibexp)}, + ); + } + + sub _dump_inc { + my($self, $inc) = @_; + + my @inc = map { qq('$_') } (@$inc, '.'); # . for inc/Module/Install.pm + + open my $out, ">$self->{base}/DumpedINC.pm" or die $!; + local $" = ","; + print $out "BEGIN { \@INC = (@inc) }\n1;\n"; + } + + sub _import_local_lib { + my($self, @args) = @_; + local $SIG{__WARN__} = sub { }; # catch 'Attempting to write ...' + local::lib->import(@args); + } + + sub setup_local_lib { + my($self, $base) = @_; + + require local::lib; + { + local $0 = 'cpanm'; # so curl/wget | perl works + $base ||= "~/perl5"; + if ($self->{self_contained}) { + my @inc = $self->_core_only_inc($base); + $self->_dump_inc(\@inc); + $self->{search_inc} = [ @inc ]; + } + $self->_import_local_lib($base); + } + + $self->bootstrap_local_lib_deps; + } + + sub bootstrap_local_lib_deps { + my $self = shift; + push @{$self->{bootstrap_deps}}, + 'ExtUtils::MakeMaker' => 6.31, + 'ExtUtils::Install' => 1.46, + 'Module::Build' => 0.36; + } + + sub prompt_bool { + my($self, $mess, $def) = @_; + + my $val = $self->prompt($mess, $def); + return lc $val eq 'y'; + } + + sub prompt { + my($self, $mess, $def) = @_; + + my $isa_tty = -t STDIN && (-t STDOUT || !(-f STDOUT || -c STDOUT)) ; + my $dispdef = defined $def ? "[$def] " : " "; + $def = defined $def ? $def : ""; + + if ($self->{quiet} || !$self->{prompt} || (!$isa_tty && eof STDIN)) { + return $def; + } + + local $|=1; + local $\; + my $ans; + eval { + local $SIG{ALRM} = sub { undef $ans; die "alarm\n" }; + print STDOUT "$mess $dispdef"; + alarm $self->{prompt_timeout} if $self->{prompt_timeout}; + $ans = <STDIN>; + alarm 0; + }; + if ( defined $ans ) { + chomp $ans; + } else { # user hit ctrl-D or alarm timeout + print STDOUT "\n"; + } + + return (!defined $ans || $ans eq '') ? $def : $ans; + } + + sub diag_ok { + my($self, $msg) = @_; + chomp $msg; + $msg ||= "OK"; + if ($self->{in_progress}) { + $self->_diag("$msg\n"); + $self->{in_progress} = 0; + } + $self->log("-> $msg\n"); + } + + sub diag_fail { + my($self, $msg) = @_; + chomp $msg; + if ($self->{in_progress}) { + $self->_diag("FAIL\n"); + $self->{in_progress} = 0; + } + + if ($msg) { + $self->_diag("! $msg\n"); + $self->log("-> FAIL $msg\n"); + } + } + + sub diag_progress { + my($self, $msg) = @_; + chomp $msg; + $self->{in_progress} = 1; + $self->_diag("$msg ... "); + $self->log("$msg\n"); + } + + sub _diag { + my $self = shift; + print STDERR @_ if $self->{verbose} or !$self->{quiet}; + } + + sub diag { + my($self, $msg) = @_; + $self->_diag($msg); + $self->log($msg); + } + + sub chat { + my $self = shift; + print STDERR @_ if $self->{verbose}; + $self->log(@_); + } + + sub log { + my $self = shift; + open my $out, ">>$self->{log}"; + print $out @_; + } + + sub run { + my($self, $cmd) = @_; + + if (WIN32 && ref $cmd eq 'ARRAY') { + $cmd = join q{ }, map { $self->shell_quote($_) } @$cmd; + } + + if (ref $cmd eq 'ARRAY') { + my $pid = fork; + if ($pid) { + waitpid $pid, 0; + return !$?; + } else { + $self->run_exec($cmd); + } + } else { + unless ($self->{verbose}) { + $cmd .= " >> " . $self->shell_quote($self->{log}) . " 2>&1"; + } + !system $cmd; + } + } + + sub run_exec { + my($self, $cmd) = @_; + + if (ref $cmd eq 'ARRAY') { + unless ($self->{verbose}) { + open my $logfh, ">>", $self->{log}; + open STDERR, '>&', $logfh; + open STDOUT, '>&', $logfh; + close $logfh; + } + exec @$cmd; + } else { + unless ($self->{verbose}) { + $cmd .= " >> " . $self->shell_quote($self->{log}) . " 2>&1"; + } + exec $cmd; + } + } + + sub run_timeout { + my($self, $cmd, $timeout) = @_; + return $self->run($cmd) if WIN32 || $self->{verbose} || !$timeout; + + my $pid = fork; + if ($pid) { + eval { + local $SIG{ALRM} = sub { die "alarm\n" }; + alarm $timeout; + waitpid $pid, 0; + alarm 0; + }; + if ($@ && $@ eq "alarm\n") { + $self->diag_fail("Timed out (> ${timeout}s). Use --verbose to retry."); + local $SIG{TERM} = 'IGNORE'; + kill TERM => 0; + waitpid $pid, 0; + return; + } + return !$?; + } elsif ($pid == 0) { + $self->run_exec($cmd); + } else { + $self->chat("! fork failed: falling back to system()\n"); + $self->run($cmd); + } + } + + sub configure { + my($self, $cmd) = @_; + + # trick AutoInstall + local $ENV{PERL5_CPAN_IS_RUNNING} = local $ENV{PERL5_CPANPLUS_IS_RUNNING} = $$; + + # e.g. skip CPAN configuration on local::lib + local $ENV{PERL5_CPANM_IS_RUNNING} = $$; + + my $use_default = !$self->{interactive}; + local $ENV{PERL_MM_USE_DEFAULT} = $use_default; + + local $self->{verbose} = $self->{verbose} || $self->{interactive}; + $self->run_timeout($cmd, $self->{configure_timeout}); + } + + sub build { + my($self, $cmd, $distname) = @_; + + return 1 if $self->run_timeout($cmd, $self->{build_timeout}); + while (1) { + my $ans = lc $self->prompt("Building $distname failed.\nYou can s)kip, r)etry or l)ook ?", "s"); + return if $ans eq 's'; + return $self->build($cmd, $distname) if $ans eq 'r'; + $self->look if $ans eq 'l'; + } + } + + sub test { + my($self, $cmd, $distname) = @_; + return 1 if $self->{notest}; + + # http://www.nntp.perl.org/group/perl.perl5.porters/2009/10/msg152656.html + local $ENV{AUTOMATED_TESTING} = 1 + unless $self->env('NO_AUTOMATED_TESTING'); + + return 1 if $self->run_timeout($cmd, $self->{test_timeout}); + if ($self->{force}) { + $self->diag_fail("Testing $distname failed but installing it anyway."); + return 1; + } else { + $self->diag_fail; + while (1) { + my $ans = lc $self->prompt("Testing $distname failed.\nYou can s)kip, r)etry, f)orce install or l)ook ?", "s"); + return if $ans eq 's'; + return $self->test($cmd, $distname) if $ans eq 'r'; + return 1 if $ans eq 'f'; + $self->look if $ans eq 'l'; + } + } + } + + sub install { + my($self, $cmd, $uninst_opts) = @_; + + if ($self->{sudo}) { + unshift @$cmd, "sudo"; + } + + if ($self->{uninstall_shadows} && !$ENV{PERL_MM_OPT}) { + push @$cmd, @$uninst_opts; + } + + $self->run($cmd); + } + + sub look { + my $self = shift; + + my $shell = $ENV{SHELL}; + $shell ||= $ENV{COMSPEC} if WIN32; + if ($shell) { + my $cwd = Cwd::cwd; + $self->diag("Entering $cwd with $shell\n"); + system $shell; + } else { + $self->diag_fail("You don't seem to have a SHELL :/"); + } + } + + sub chdir { + my $self = shift; + chdir(File::Spec->canonpath($_[0])) or die "$_[0]: $!"; + } + + sub configure_mirrors { + my $self = shift; + unless (@{$self->{mirrors}}) { + $self->{mirrors} = [ 'http://search.cpan.org/CPAN' ]; + } + for (@{$self->{mirrors}}) { + s!^/!file:///!; + s!/$!!; + } + } + + sub self_upgrade { + my $self = shift; + $self->{argv} = [ 'App::cpanminus' ]; + return; # continue + } + + sub install_module { + my($self, $module, $depth) = @_; + + if ($self->{seen}{$module}++) { + $self->chat("Already tried $module. Skipping.\n"); + return 1; + } + + my $dist = $self->resolve_name($module); + unless ($dist) { + $self->diag_fail("Couldn't find module or a distribution $module"); + return; + } + + if ($dist->{distvname} && $self->{seen}{$dist->{distvname}}++) { + $self->chat("Already tried $dist->{distvname}. Skipping.\n"); + return 1; + } + + if ($dist->{source} eq 'cpan') { + $dist->{meta} = $self->fetch_meta($dist); + } + + if ($self->{cmd} eq 'info') { + print $dist->{cpanid}, "/", $dist->{filename}, "\n"; + return 1; + } + + $self->check_libs; + + if ($dist->{module}) { + my($ok, $local) = $self->check_module($dist->{module}, $dist->{module_version} || 0); + if ($self->{skip_installed} && $ok) { + $self->diag("$dist->{module} is up to date. ($local)\n"); + return 1; + } + } + + if ($dist->{dist} eq 'perl'){ + $self->diag("skipping $dist->{pathname}\n"); + return 1; + } + + $self->diag("--> Working on $module\n"); + + $dist->{dir} ||= $self->fetch_module($dist); + + unless ($dist->{dir}) { + $self->diag_fail("Failed to fetch distribution $dist->{distvname}"); + return; + } + + $self->chat("Entering $dist->{dir}\n"); + $self->chdir($self->{base}); + $self->chdir($dist->{dir}); + + if ($self->{cmd} eq 'look') { + $self->look; + return 1; + } + + return $self->build_stuff($module, $dist, $depth); + } + + sub fetch_module { + my($self, $dist) = @_; + + $self->chdir($self->{base}); + + for my $uri (@{$dist->{uris}}) { + $self->diag_progress("Fetching $uri"); + + # Ugh, $dist->{filename} can contain sub directory + my $filename = $dist->{filename} || $uri; + my $name = File::Basename::basename($filename); + + my $cancelled; + my $fetch = sub { + my $file; + eval { + local $SIG{INT} = sub { $cancelled = 1; die "SIGINT\n" }; + $self->mirror($uri, $name); + $file = $name if -e $name; + }; + $self->chat("$@") if $@ && $@ ne "SIGINT\n"; + return $file; + }; + + my($try, $file); + while ($try++ < 3) { + $file = $fetch->(); + last if $cancelled or $file; + $self->diag_fail("Download $uri failed. Retrying ... "); + } + + if ($cancelled) { + $self->diag_fail("Download cancelled."); + return; + } + + unless ($file) { + $self->diag_fail("Failed to download $uri"); + next; + } + + $self->diag_ok; + + my $dir = $self->unpack($file); + next unless $dir; # unpack failed + + return $dist, $dir; + } + } + + sub unpack { + my($self, $file) = @_; + $self->chat("Unpacking $file\n"); + my $dir = $file =~ /\.zip/i ? $self->unzip($file) : $self->untar($file); + unless ($dir) { + $self->diag_fail("Failed to unpack $file: no directory"); + } + return $dir; + } + + sub resolve_name { + my($self, $module) = @_; + + # URL + if ($module =~ /^(ftp|https?|file):/) { + if ($module =~ m!authors/id/!) { + return $self->cpan_dist($module, $module); + } else { + return { uris => [ $module ] }; + } + } + + # Directory + if ($module =~ m!^[\./]! && -d $module) { + return { + source => 'local', + dir => Cwd::abs_path($module), + }; + } + + # File + if (-f $module) { + return { + source => 'local', + uris => [ "file://" . Cwd::abs_path($module) ], + }; + } + + # cpan URI + if ($module =~ s!^cpan:///distfile/!!) { + return $self->cpan_dist($module); + } + + # PAUSEID/foo + if ($module =~ m!([A-Z]{3,})/!) { + return $self->cpan_dist($module); + } + + # Module name + return $self->search_module($module); + } + + sub cpan_module { + my($self, $module, $dist, $version) = @_; + + my $dist = $self->cpan_dist($dist); + $dist->{module} = $module; + $dist->{module_version} = $version if $version && $version ne 'undef'; + + return $dist; + } + + sub cpan_dist { + my($self, $dist, $url) = @_; + + $dist =~ s!^([A-Z]{3})!substr($1,0,1)."/".substr($1,0,2)."/".$1!e; + + require CPAN::DistnameInfo; + my $d = CPAN::DistnameInfo->new($dist); + + if ($url) { + $url = [ $url ] unless ref $url eq 'ARRAY'; + } else { + my $id = $d->cpanid; + my $fn = substr($id, 0, 1) . "/" . substr($id, 0, 2) . "/" . $id . "/" . $d->filename; + + my @mirrors = @{$self->{mirrors}}; + my @urls = map "$_/authors/id/$fn", @mirrors; + + $url = \@urls, + } + + return { + $d->properties, + source => 'cpan', + uris => $url, + }; + } + + sub check_module { + my($self, $mod, $want_ver) = @_; + + require Module::Metadata; + my $meta = Module::Metadata->new_from_module($mod, inc => $self->{search_inc}) + or return 0, undef; + + my $version = $meta->version; + + # When -L is in use, the version loaded from 'perl' library path + # might be newer than the version that is shipped with the current perl + if ($self->{self_contained} && $self->loaded_from_perl_lib($meta)) { + require Module::CoreList; + $version = $Module::CoreList::version{$]}{$mod}; + } + + $self->{local_versions}{$mod} = $version; + + if ($self->is_deprecated($meta)){ + return 0, $version; + } elsif (!$want_ver or $version >= version->new($want_ver)) { + return 1, $version; + } else { + return 0, $version; + } + } + + sub is_deprecated { + my($self, $meta) = @_; + + my $deprecated = eval { + require Module::CoreList; + Module::CoreList::is_deprecated($meta->{module}); + }; + + return unless $deprecated; + return $self->loaded_from_perl_lib($meta); + } + + sub loaded_from_perl_lib { + my($self, $meta) = @_; + + require Config; + for my $dir (qw(archlibexp privlibexp)) { + my $confdir = $Config{$dir}; + if ($confdir eq substr($meta->filename, 0, length($confdir))) { + return 1; + } + } + + return; + } + + sub should_install { + my($self, $mod, $ver) = @_; + + $self->chat("Checking if you have $mod $ver ... "); + my($ok, $local) = $self->check_module($mod, $ver); + + if ($ok) { $self->chat("Yes ($local)\n") } + elsif ($local) { $self->chat("No ($local < $ver)\n") } + else { $self->chat("No\n") } + + return $mod unless $ok; + return; + } + + sub install_deps { + my($self, $dir, $depth, @deps) = @_; + + my(@install, %seen); + while (my($mod, $ver) = splice @deps, 0, 2) { + next if $seen{$mod} or $mod eq 'perl' or $mod eq 'Config'; + if ($self->should_install($mod, $ver)) { + push @install, $mod; + $seen{$mod} = 1; + } + } + + if (@install) { + $self->diag("==> Found dependencies: " . join(", ", @install) . "\n"); + } + + my @fail; + for my $mod (@install) { + $self->install_module($mod, $depth + 1) + or push @fail, $mod; + } + + $self->chdir($self->{base}); + $self->chdir($dir) if $dir; + + return @fail; + } + + sub install_deps_bailout { + my($self, $target, $dir, $depth, @deps) = @_; + + my @fail = $self->install_deps($dir, $depth, @deps); + if (@fail) { + unless ($self->prompt_bool("Installing the following dependencies failed:\n==> " . + join(", ", @fail) . "\nDo you want to continue building $target anyway?", "n")) { + $self->diag_fail("Bailing out the installation for $target. Retry with --prompt or --force."); + return; + } + } + + return 1; + } + + sub _patch_module_build_config_deps { + my($self, $config_deps) = @_; + + # Crazy hack to auto-add Module::Build dependencies into + # configure_requires if Module::Build is there, since there's a + # possibility where Module::Build is in 'perl' library path while + # the dependencies are in 'site' and can't be loaded when -L + # (--local-lib-contained) is in effect. + + my %config_deps = (@{$config_deps}); + if ($config_deps{"Module::Build"}) { + push @{$config_deps}, ( + 'Perl::OSType' => 1, + 'Module::Metadata' => 1.000002, + 'version' => 0.87, + ); + } + } + + sub build_stuff { + my($self, $stuff, $dist, $depth) = @_; + + my @config_deps; + if (!%{$dist->{meta} || {}} && -e 'META.yml') { + $self->chat("Checking configure dependencies from META.yml\n"); + $dist->{meta} = $self->parse_meta('META.yml'); + } + + push @config_deps, %{$dist->{meta}{configure_requires} || {}}; + + my $target = $dist->{meta}{name} ? "$dist->{meta}{name}-$dist->{meta}{version}" : $dist->{dir}; + + $self->_patch_module_build_config_deps(\@config_deps) + if $self->{self_contained}; + + $self->install_deps_bailout($target, $dist->{dir}, $depth, @config_deps) + or return; + + $self->diag_progress("Configuring $target"); + + my $configure_state = $self->configure_this($dist); + + $self->diag_ok($configure_state->{configured_ok} ? "OK" : "N/A"); + + my @deps = $self->find_prereqs($dist->{meta}); + + my $distname = $dist->{meta}{name} ? "$dist->{meta}{name}-$dist->{meta}{version}" : $stuff; + + $self->install_deps_bailout($distname, $dist->{dir}, $depth, @deps) + or return; + + if ($self->{installdeps} && $depth == 0) { + $self->diag("<== Installed dependencies for $stuff. Finishing.\n"); + return 1; + } + + my $installed; + if ($configure_state->{use_module_build} && -e 'Build' && -f _) { + $self->diag_progress("Building " . ($self->{notest} ? "" : "and testing ") . $distname); + $self->build([ $self->{perl}, "./Build" ], $distname) && + $self->test([ $self->{perl}, "./Build", "test" ], $distname) && + $self->install([ $self->{perl}, "./Build", "install" ], [ "--uninst", 1 ]) && + $installed++; + } elsif ($self->{make} && -e 'Makefile') { + $self->diag_progress("Building " . ($self->{notest} ? "" : "and testing ") . $distname); + $self->build([ $self->{make} ], $distname) && + $self->test([ $self->{make}, "test" ], $distname) && + $self->install([ $self->{make}, "install" ], [ "UNINST=1" ]) && + $installed++; + } else { + my $why; + my $configure_failed = $configure_state->{configured} && !$configure_state->{configured_ok}; + if ($configure_failed) { $why = "Configure failed for $distname." } + elsif ($self->{make}) { $why = "The distribution doesn't have a proper Makefile.PL/Build.PL" } + else { $why = "Can't configure the distribution. You probably need to have 'make'." } + + $self->diag_fail("$why See $self->{log} for details."); + return; + } + + if ($installed) { + my $local = $self->{local_versions}{$dist->{module} || ''}; + my $version = $dist->{module_version} || $dist->{meta}{version} || $dist->{version}; + my $reinstall = $local && ($local eq $version); + + my $how = $reinstall ? "reinstalled $distname" + : $local ? "installed $distname (upgraded from $local)" + : "installed $distname" ; + my $msg = "Successfully $how"; + $self->diag_ok; + $self->diag("$msg\n"); + return 1; + } else { + my $msg = "Building $distname failed"; + $self->diag_fail("Installing $stuff failed. See $self->{log} for details."); + return; + } + } + + sub configure_this { + my($self, $dist) = @_; + + my @switches; + @switches = ("-I$self->{base}", "-MDumpedINC") if $self->{self_contained}; + local $ENV{PERL5LIB} = '' if $self->{self_contained}; + + my $state = {}; + + my $try_eumm = sub { + if (-e 'Makefile.PL') { + $self->chat("Running Makefile.PL\n"); + local $ENV{X_MYMETA} = 'YAML'; + + # NOTE: according to Devel::CheckLib, most XS modules exit + # with 0 even if header files are missing, to avoid receiving + # tons of FAIL reports in such cases. So exit code can't be + # trusted if it went well. + if ($self->configure([ $self->{perl}, @switches, "Makefile.PL" ])) { + $state->{configured_ok} = -e 'Makefile'; + } + $state->{configured}++; + } + }; + + my $try_mb = sub { + if (-e 'Build.PL') { + $self->chat("Running Build.PL\n"); + if ($self->configure([ $self->{perl}, @switches, "Build.PL" ])) { + $state->{configured_ok} = -e 'Build' && -f _; + } + $state->{use_module_build}++; + $state->{configured}++; + } + }; + + # Module::Build deps should use MakeMaker because that causes circular deps and fail + # Otherwise we should prefer Build.PL + my %should_use_mm = map { $_ => 1 } qw( version ExtUtils-ParseXS ExtUtils-Install ExtUtils-Manifest ); + + my @try; + if ($dist->{dist} && $should_use_mm{$dist->{dist}}) { + @try = ($try_eumm, $try_mb); + } else { + @try = ($try_mb, $try_eumm); + } + + for my $try (@try) { + $try->(); + last if $state->{configured_ok}; + } + + unless ($state->{configured_ok}) { + while (1) { + my $ans = lc $self->prompt("Configuring $dist->{dist} failed.\nYou can s)kip, r)etry or l)ook ?", "s"); + last if $ans eq 's'; + return $self->configure_this($dist) if $ans eq 'r'; + $self->look if $ans eq 'l'; + } + } + + return $state; + } + + sub safe_eval { + my($self, $code) = @_; + eval $code; + } + + sub find_prereqs { + my($self, $meta) = @_; + + my @deps; + if (-e 'MYMETA.yml') { + $self->chat("Checking dependencies from MYMETA.yml ...\n"); + my $mymeta = $self->parse_meta('MYMETA.yml'); + @deps = $self->extract_requires($mymeta); + $meta->{$_} = $mymeta->{$_} for keys %$mymeta; # merge + } elsif (-e '_build/prereqs') { + $self->chat("Checking dependencies from _build/prereqs ...\n"); + my $mymeta = do { open my $in, "_build/prereqs"; $self->safe_eval(join "", <$in>) }; + @deps = $self->extract_requires($mymeta); + } + + if (-e 'Makefile') { + $self->chat("Finding PREREQ from Makefile ...\n"); + open my $mf, "Makefile"; + while (<$mf>) { + if (/^\#\s+PREREQ_PM => {\s*(.*?)\s*}/) { + my @all; + my @pairs = split ', ', $1; + for (@pairs) { + my ($pkg, $v) = split '=>', $_; + push @all, [ $pkg, $v ]; + } + my $list = join ", ", map { "'$_->[0]' => $_->[1]" } @all; + my $prereq = $self->safe_eval("no strict; +{ $list }"); + push @deps, %$prereq if $prereq; + last; + } + } + } + + # No need to remove, but this gets in the way of signature testing :/ + unlink 'MYMETA.yml'; + + return @deps; + } + + sub extract_requires { + my($self, $meta) = @_; + + my @deps; + push @deps, %{$meta->{requires}} if $meta->{requires}; + push @deps, %{$meta->{build_requires}} if $meta->{build_requires}; + + return @deps; + } + + sub cleanup_workdirs { + my $self = shift; + + my $expire = time - 24 * 60 * 60 * $self->{auto_cleanup}; + my @targets; + + opendir my $dh, "$self->{home}/work"; + while (my $e = readdir $dh) { + next if $e !~ /^(\d+)\.\d+$/; # {UNIX time}.{PID} + my $time = $1; + if ($time < $expire) { + push @targets, "$self->{home}/work/$e"; + } + } + + if (@targets) { + $self->chat("Expiring ", scalar(@targets), " work directories.\n"); + File::Path::rmtree(\@targets, 0, 0); # safe = 0, since blib usually doesn't have write bits + } + } + + sub DESTROY { + my $self = shift; + $self->{at_exit}->($self) if $self->{at_exit}; + } + + # Utils + + sub shell_quote { + my($self, $stuff) = @_; + $quote . $stuff . $quote; + } + + sub which { + my($self, $name) = @_; + my $exe_ext = $Config{_exe}; + for my $dir (File::Spec->path) { + my $fullpath = File::Spec->catfile($dir, $name); + if (-x $fullpath || -x ($fullpath .= $exe_ext)) { + if ($fullpath =~ /\s/ && $fullpath !~ /^$quote/) { + $fullpath = $self->shell_quote($fullpath); + } + return $fullpath; + } + } + return; + } + + sub get { $_[0]->{_backends}{get}->(@_) }; + sub mirror { $_[0]->{_backends}{mirror}->(@_) }; + sub untar { $_[0]->{_backends}{untar}->(@_) }; + sub unzip { $_[0]->{_backends}{unzip}->(@_) }; + + sub file_get { + my($self, $uri) = @_; + open my $fh, "<$uri" or return; + join '', <$fh>; + } + + sub file_mirror { + my($self, $uri, $path) = @_; + File::Copy::copy($uri, $path); + } + + sub init_tools { + my $self = shift; + + return if $self->{initialized}++; + + if ($self->{make} = $self->which($Config{make})) { + $self->chat("You have make $self->{make}\n"); + } + + # use --no-lwp if they have a broken LWP, to upgrade LWP + if ($self->{try_lwp} && eval { require LWP::UserAgent; LWP::UserAgent->VERSION(5.802) }) { + $self->chat("You have LWP $LWP::VERSION\n"); + my $ua = sub { + LWP::UserAgent->new( + parse_head => 0, + env_proxy => 1, + agent => "cpanminus/$VERSION", + timeout => 30, + @_, + ); + }; + $self->{_backends}{get} = sub { + my $self = shift; + my $res = $ua->()->request(HTTP::Request->new(GET => $_[0])); + return unless $res->is_success; + return $res->decoded_content; + }; + $self->{_backends}{mirror} = sub { + my $self = shift; + my $res = $ua->()->mirror(@_); + $res->code; + }; + } elsif ($self->{try_wget} and my $wget = $self->which('wget')) { + $self->chat("You have $wget\n"); + $self->{_backends}{get} = sub { + my($self, $uri) = @_; + return $self->file_get($uri) if $uri =~ s!^file:/+!/!; + my $q = $self->{verbose} ? '' : '-q'; + open my $fh, "$wget $uri $q -O - |" or die "wget $uri: $!"; + local $/; + <$fh>; + }; + $self->{_backends}{mirror} = sub { + my($self, $uri, $path) = @_; + return $self->file_mirror($uri, $path) if $uri =~ s!^file:/+!/!; + my $q = $self->{verbose} ? '' : '-q'; + system "$wget --retry-connrefused $uri $q -O $path"; + }; + } elsif ($self->{try_curl} and my $curl = $self->which('curl')) { + $self->chat("You have $curl\n"); + $self->{_backends}{get} = sub { + my($self, $uri) = @_; + return $self->file_get($uri) if $uri =~ s!^file:/+!/!; + my $q = $self->{verbose} ? '' : '-s'; + open my $fh, "$curl -L $q $uri |" or die "curl $uri: $!"; + local $/; + <$fh>; + }; + $self->{_backends}{mirror} = sub { + my($self, $uri, $path) = @_; + return $self->file_mirror($uri, $path) if $uri =~ s!^file:/+!/!; + my $q = $self->{verbose} ? '' : '-s'; + system "$curl -L $uri $q -# -o $path"; + }; + } else { + require HTTP::Tiny; + $self->chat("Falling back to HTTP::Tiny $HTTP::Tiny::VERSION\n"); + + $self->{_backends}{get} = sub { + my $self = shift; + my $res = HTTP::Tiny->new->get($_[0]); + return unless $res->{success}; + return $res->{content}; + }; + $self->{_backends}{mirror} = sub { + my $self = shift; + my $res = HTTP::Tiny->new->mirror(@_); + return $res->{status}; + }; + } + + my $tar = $self->which('tar'); + my $tar_ver; + my $maybe_bad_tar = sub { WIN32 || SUNOS || (($tar_ver = `$tar --version 2>/dev/null`) =~ /GNU.*1\.13/i) }; + + if ($tar && !$maybe_bad_tar->()) { + chomp $tar_ver; + $self->chat("You have $tar: $tar_ver\n"); + $self->{_backends}{untar} = sub { + my($self, $tarfile) = @_; + + my $xf = "xf" . ($self->{verbose} ? 'v' : ''); + my $ar = $tarfile =~ /bz2$/ ? 'j' : 'z'; + + my($root, @others) = `$tar tf$ar $tarfile` + or return undef; + + chomp $root; + $root =~ s{^(.+?)/.*$}{$1}; + + system "$tar $xf$ar $tarfile"; + return $root if -d $root; + + $self->diag_fail("Bad archive: $tarfile"); + return undef; + } + } elsif ( $tar + and my $gzip = $self->which('gzip') + and my $bzip2 = $self->which('bzip2')) { + $self->chat("You have $tar, $gzip and $bzip2\n"); + $self->{_backends}{untar} = sub { + my($self, $tarfile) = @_; + + my $x = "x" . ($self->{verbose} ? 'v' : '') . "f -"; + my $ar = $tarfile =~ /bz2$/ ? $bzip2 : $gzip; + + my($root, @others) = `$ar -dc $tarfile | $tar tf -` + or return undef; + + chomp $root; + $root =~ s{^(.+?)/.*$}{$1}; + + system "$ar -dc $tarfile | $tar $x"; + return $root if -d $root; + + $self->diag_fail("Bad archive: $tarfile"); + return undef; + } + } elsif (eval { require Archive::Tar }) { # uses too much memory! + $self->chat("Falling back to Archive::Tar $Archive::Tar::VERSION\n"); + $self->{_backends}{untar} = sub { + my $self = shift; + my $t = Archive::Tar->new($_[0]); + my $root = ($t->list_files)[0]; + $root =~ s{^(.+?)/.*$}{$1}; + $t->extract; + return -d $root ? $root : undef; + }; + } else { + $self->{_backends}{untar} = sub { + die "Failed to extract $_[1] - You need to have tar or Archive::Tar installed.\n"; + }; + } + + if (my $unzip = $self->which('unzip')) { + $self->chat("You have $unzip\n"); + $self->{_backends}{unzip} = sub { + my($self, $zipfile) = @_; + + my $opt = $self->{verbose} ? '' : '-q'; + my(undef, $root, @others) = `$unzip -t $zipfile` + or return undef; + + chomp $root; + $root =~ s{^\s+testing:\s+(.+?)/\s+OK$}{$1}; + + system "$unzip $opt $zipfile"; + return $root if -d $root; + + $self->diag_fail("Bad archive: [$root] $zipfile"); + return undef; + } + } else { + $self->{_backends}{unzip} = sub { + eval { require Archive::Zip } + or die "Failed to extract $_[1] - You need to have unzip or Archive::Zip installed.\n"; + my($self, $file) = @_; + my $zip = Archive::Zip->new(); + my $status; + $status = $zip->read($file); + $self->diag_fail("Read of file[$file] failed") + if $status != Archive::Zip::AZ_OK(); + my @members = $zip->members(); + my $root; + for my $member ( @members ) { + my $af = $member->fileName(); + next if ($af =~ m!^(/|\.\./)!); + $root = $af unless $root; + $status = $member->extractToFileNamed( $af ); + $self->diag_fail("Extracting of file[$af] from zipfile[$file failed") + if $status != Archive::Zip::AZ_OK(); + } + return -d $root ? $root : undef; + }; + } + } + + sub parse_meta { + my($self, $file) = @_; + return eval { (Parse::CPAN::Meta::LoadFile($file))[0] } || {}; + } + + sub parse_meta_string { + my($self, $yaml) = @_; + return eval { (Parse::CPAN::Meta::Load($yaml))[0] } || {}; + } + + 1; +APP_CPANMINUS_SCRIPT + +$fatpacked{"CPAN/DistnameInfo.pm"} = <<'CPAN_DISTNAMEINFO'; + + package CPAN::DistnameInfo; + + $VERSION = "0.11"; + use strict; + + sub distname_info { + my $file = shift or return; + + my ($dist, $version) = $file =~ /^ + ((?:[-+.]*(?:[A-Za-z0-9]+|(?<=\D)_|_(?=\D))* + (?: + [A-Za-z](?=[^A-Za-z]|$) + | + \d(?=-) + )(?<![._-][vV]) + )+)(.*) + $/xs or return ($file,undef,undef); + + if ($dist =~ /-undef\z/ and ! length $version) { + $dist =~ s/-undef\z//; + } + + # Remove potential -withoutworldwriteables suffix + $version =~ s/-withoutworldwriteables$//; + + if ($version =~ /^(-[Vv].*)-(\d.*)/) { + + # Catch names like Unicode-Collate-Standard-V3_1_1-0.1 + # where the V3_1_1 is part of the distname + $dist .= $1; + $version = $2; + } + + # Normalize the Dist.pm-1.23 convention which CGI.pm and + # a few others use. + $dist =~ s{\.pm$}{}; + + $version = $1 + if !length $version and $dist =~ s/-(\d+\w)$//; + + $version = $1 . $version + if $version =~ /^\d+$/ and $dist =~ s/-(\w+)$//; + + if ($version =~ /\d\.\d/) { + $version =~ s/^[-_.]+//; + } + else { + $version =~ s/^[-_]+//; + } + + my $dev; + if (length $version) { + if ($file =~ /^perl-?\d+\.(\d+)(?:\D(\d+))?(-(?:TRIAL|RC)\d+)?$/) { + $dev = 1 if (($1 > 6 and $1 & 1) or ($2 and $2 >= 50)) or $3; + } + elsif ($version =~ /\d\D\d+_\d/ or $version =~ /-TRIAL/) { + $dev = 1; + } + } + else { + $version = undef; + } + + ($dist, $version, $dev); + } + + sub new { + my $class = shift; + my $distfile = shift; + + $distfile =~ s,//+,/,g; + + my %info = ( pathname => $distfile ); + + ($info{filename} = $distfile) =~ s,^(((.*?/)?authors/)?id/)?([A-Z])/(\4[A-Z])/(\5[-A-Z0-9]*)/,, + and $info{cpanid} = $6; + + if ($distfile =~ m,([^/]+)\.(tar\.(?:g?z|bz2)|zip|tgz)$,i) { # support more ? + $info{distvname} = $1; + $info{extension} = $2; + } + + @info{qw(dist version beta)} = distname_info($info{distvname}); + $info{maturity} = delete $info{beta} ? 'developer' : 'released'; + + return bless \%info, $class; + } + + sub dist { shift->{dist} } + sub version { shift->{version} } + sub maturity { shift->{maturity} } + sub filename { shift->{filename} } + sub cpanid { shift->{cpanid} } + sub distvname { shift->{distvname} } + sub extension { shift->{extension} } + sub pathname { shift->{pathname} } + + sub properties { %{ $_[0] } } + + 1; + + __END__ + + =head1 NAME + + CPAN::DistnameInfo - Extract distribution name and version from a distribution filename + + =head1 SYNOPSIS + + my $pathname = "authors/id/G/GB/GBARR/CPAN-DistnameInfo-0.02.tar.gz"; + + my $d = CPAN::DistnameInfo->new($pathname); + + my $dist = $d->dist; # "CPAN-DistnameInfo" + my $version = $d->version; # "0.02" + my $maturity = $d->maturity; # "released" + my $filename = $d->filename; # "CPAN-DistnameInfo-0.02.tar.gz" + my $cpanid = $d->cpanid; # "GBARR" + my $distvname = $d->distvname; # "CPAN-DistnameInfo-0.02" + my $extension = $d->extension; # "tar.gz" + my $pathname = $d->pathname; # "authors/id/G/GB/GBARR/..." + + my %prop = $d->properties; + + =head1 DESCRIPTION + + Many online services that are centered around CPAN attempt to + associate multiple uploads by extracting a distribution name from + the filename of the upload. For most distributions this is easy as + they have used ExtUtils::MakeMaker or Module::Build to create the + distribution, which results in a uniform name. But sadly not all + uploads are created in this way. + + C<CPAN::DistnameInfo> uses heuristics that have been learnt by + L<http://search.cpan.org/> to extract the distribution name and + version from filenames and also report if the version is to be + treated as a developer release + + The constructor takes a single pathname, returning an object with the following methods + + =over + + =item cpanid + + If the path given looked like a CPAN authors directory path, then this will be the + the CPAN id of the author. + + =item dist + + The name of the distribution + + =item distvname + + The file name with any suffix and leading directory names removed + + =item filename + + If the path given looked like a CPAN authors directory path, then this will be the + path to the file relative to the detected CPAN author directory. Otherwise it is the path + that was passed in. + + =item maturity + + The maturity of the distribution. This will be either C<released> or C<developer> + + =item extension + + The extension of the distribution, often used to denote the archive type (e.g. 'tar.gz') + + =item pathname + + The pathname that was passed to the constructor when creating the object. + + =item properties + + This will return a list of key-value pairs, suitable for assigning to a hash, + for the known properties. + + =item version + + The extracted version + + =back + + =head1 AUTHOR + + Graham Barr <gbarr@pobox.com> + + =head1 COPYRIGHT + + Copyright (c) 2003 Graham Barr. All rights reserved. This program is + free software; you can redistribute it and/or modify it under the same + terms as Perl itself. + + =cut + +CPAN_DISTNAMEINFO + +$fatpacked{"HTTP/Tiny.pm"} = <<'HTTP_TINY'; + # vim: ts=4 sts=4 sw=4 et: + # + # This file is part of HTTP-Tiny + # + # This software is copyright (c) 2011 by Christian Hansen. + # + # This is free software; you can redistribute it and/or modify it under + # the same terms as the Perl 5 programming language system itself. + # + package HTTP::Tiny; + BEGIN { + $HTTP::Tiny::VERSION = '0.009'; + } + use strict; + use warnings; + # ABSTRACT: A small, simple, correct HTTP/1.1 client + + use Carp (); + + + my @attributes; + BEGIN { + @attributes = qw(agent default_headers max_redirect max_size proxy timeout); + no strict 'refs'; + for my $accessor ( @attributes ) { + *{$accessor} = sub { + @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor}; + }; + } + } + + sub new { + my($class, %args) = @_; + (my $agent = $class) =~ s{::}{-}g; + my $self = { + agent => $agent . "/" . ($class->VERSION || 0), + max_redirect => 5, + timeout => 60, + }; + for my $key ( @attributes ) { + $self->{$key} = $args{$key} if exists $args{$key} + } + return bless $self, $class; + } + + + sub get { + my ($self, $url, $args) = @_; + @_ == 2 || (@_ == 3 && ref $args eq 'HASH') + or Carp::croak(q/Usage: $http->get(URL, [HASHREF])/); + return $self->request('GET', $url, $args || {}); + } + + + sub mirror { + my ($self, $url, $file, $args) = @_; + @_ == 3 || (@_ == 4 && ref $args eq 'HASH') + or Carp::croak(q/Usage: $http->mirror(URL, FILE, [HASHREF])/); + if ( -e $file and my $mtime = (stat($file))[9] ) { + $args->{headers}{'if-modified-since'} ||= $self->_http_date($mtime); + } + my $tempfile = $file . int(rand(2**31)); + open my $fh, ">", $tempfile + or Carp::croak(qq/Error: Could not open temporary file $tempfile for downloading: $!/); + $args->{data_callback} = sub { print {$fh} $_[0] }; + my $response = $self->request('GET', $url, $args); + close $fh + or Carp::croak(qq/Error: Could not close temporary file $tempfile: $!/); + if ( $response->{success} ) { + rename $tempfile, $file + or Carp::croak "Error replacing $file with $tempfile: $!\n"; + my $lm = $response->{headers}{'last-modified'}; + if ( $lm and my $mtime = $self->_parse_http_date($lm) ) { + utime $mtime, $mtime, $file; + } + } + $response->{success} ||= $response->{status} eq '304'; + unlink $tempfile; + return $response; + } + + + my %idempotent = map { $_ => 1 } qw/GET HEAD PUT DELETE OPTIONS TRACE/; + + sub request { + my ($self, $method, $url, $args) = @_; + @_ == 3 || (@_ == 4 && ref $args eq 'HASH') + or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/); + $args ||= {}; # we keep some state in this during _request + + # RFC 2616 Section 8.1.4 mandates a single retry on broken socket + my $response; + for ( 0 .. 1 ) { + $response = eval { $self->_request($method, $url, $args) }; + last unless $@ && $idempotent{$method} + && $@ =~ m{^(?:Socket closed|Unexpected end)}; + } + + if (my $e = "$@") { + $response = { + success => q{}, + status => 599, + reason => 'Internal Exception', + content => $e, + headers => { + 'content-type' => 'text/plain', + 'content-length' => length $e, + } + }; + } + return $response; + } + + my %DefaultPort = ( + http => 80, + https => 443, + ); + + sub _request { + my ($self, $method, $url, $args) = @_; + + my ($scheme, $host, $port, $path_query) = $self->_split_url($url); + + my $request = { + method => $method, + scheme => $scheme, + host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"), + uri => $path_query, + headers => {}, + }; + + my $handle = HTTP::Tiny::Handle->new(timeout => $self->{timeout}); + + if ($self->{proxy}) { + $request->{uri} = "$scheme://$request->{host_port}$path_query"; + croak(qq/HTTPS via proxy is not supported/) + if $request->{scheme} eq 'https'; + $handle->connect(($self->_split_url($self->{proxy}))[0..2]); + } + else { + $handle->connect($scheme, $host, $port); + } + + $self->_prepare_headers_and_cb($request, $args); + $handle->write_request($request); + + my $response; + do { $response = $handle->read_response_header } + until (substr($response->{status},0,1) ne '1'); + + if ( my @redir_args = $self->_maybe_redirect($request, $response, $args) ) { + $handle->close; + return $self->_request(@redir_args, $args); + } + + if ($method eq 'HEAD' || $response->{status} =~ /^[23]04/) { + # response has no message body + } + else { + my $data_cb = $self->_prepare_data_cb($response, $args); + $handle->read_body($data_cb, $response); + } + + $handle->close; + $response->{success} = substr($response->{status},0,1) eq '2'; + return $response; + } + + sub _prepare_headers_and_cb { + my ($self, $request, $args) = @_; + + for ($self->{default_headers}, $args->{headers}) { + next unless defined; + while (my ($k, $v) = each %$_) { + $request->{headers}{lc $k} = $v; + } + } + $request->{headers}{'host'} = $request->{host_port}; + $request->{headers}{'connection'} = "close"; + $request->{headers}{'user-agent'} ||= $self->{agent}; + + if (defined $args->{content}) { + $request->{headers}{'content-type'} ||= "application/octet-stream"; + if (ref $args->{content} eq 'CODE') { + $request->{headers}{'transfer-encoding'} = 'chunked' + unless $request->{headers}{'content-length'} + || $request->{headers}{'transfer-encoding'}; + $request->{cb} = $args->{content}; + } + else { + my $content = $args->{content}; + if ( $] ge '5.008' ) { + utf8::downgrade($content, 1) + or Carp::croak(q/Wide character in request message body/); + } + $request->{headers}{'content-length'} = length $content + unless $request->{headers}{'content-length'} + || $request->{headers}{'transfer-encoding'}; + $request->{cb} = sub { substr $content, 0, length $content, '' }; + } + $request->{trailer_cb} = $args->{trailer_callback} + if ref $args->{trailer_callback} eq 'CODE'; + } + return; + } + + sub _prepare_data_cb { + my ($self, $response, $args) = @_; + my $data_cb = $args->{data_callback}; + $response->{content} = ''; + + if (!$data_cb || $response->{status} !~ /^2/) { + if (defined $self->{max_size}) { + $data_cb = sub { + $_[1]->{content} .= $_[0]; + die(qq/Size of response body exceeds the maximum allowed of $self->{max_size}\n/) + if length $_[1]->{content} > $self->{max_size}; + }; + } + else { + $data_cb = sub { $_[1]->{content} .= $_[0] }; + } + } + return $data_cb; + } + + sub _maybe_redirect { + my ($self, $request, $response, $args) = @_; + my $headers = $response->{headers}; + my ($status, $method) = ($response->{status}, $request->{method}); + if (($status eq '303' or ($status =~ /^30[127]/ && $method =~ /^GET|HEAD$/)) + and $headers->{location} + and ++$args->{redirects} <= $self->{max_redirect} + ) { + my $location = ($headers->{location} =~ /^\//) + ? "$request->{scheme}://$request->{host_port}$headers->{location}" + : $headers->{location} ; + return (($status eq '303' ? 'GET' : $method), $location); + } + return; + } + + sub _split_url { + my $url = pop; + + # URI regex adapted from the URI module + my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)> + or Carp::croak(qq/Cannot parse URL: '$url'/); + + $scheme = lc $scheme; + $path_query = "/$path_query" unless $path_query =~ m<\A/>; + + my $host = (length($authority)) ? lc $authority : 'localhost'; + $host =~ s/\A[^@]*@//; # userinfo + my $port = do { + $host =~ s/:([0-9]*)\z// && length $1 + ? $1 + : ($scheme eq 'http' ? 80 : $scheme eq 'https' ? 443 : undef); + }; + + return ($scheme, $host, $port, $path_query); + } + + # Date conversions adapted from HTTP::Date + my $DoW = "Sun|Mon|Tue|Wed|Thu|Fri|Sat"; + my $MoY = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"; + sub _http_date { + my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($_[1]); + return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", + substr($DoW,$wday*4,3), + $mday, substr($MoY,$mon*4,3), $year+1900, + $hour, $min, $sec + ); + } + + sub _parse_http_date { + my ($self, $str) = @_; + require Time::Local; + my @tl_parts; + if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) { + @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); + } + elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) { + @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); + } + elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) { + @tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6); + } + return eval { + my $t = @tl_parts ? Time::Local::timegm(@tl_parts) : -1; + $t < 0 ? undef : $t; + }; + } + + package + HTTP::Tiny::Handle; # hide from PAUSE/indexers + use strict; + use warnings; + + use Carp qw[croak]; + use Errno qw[EINTR EPIPE]; + use IO::Socket qw[SOCK_STREAM]; + + sub BUFSIZE () { 32768 } + + my $Printable = sub { + local $_ = shift; + s/\r/\\r/g; + s/\n/\\n/g; + s/\t/\\t/g; + s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge; + $_; + }; + + my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/; + + sub new { + my ($class, %args) = @_; + return bless { + rbuf => '', + timeout => 60, + max_line_size => 16384, + max_header_lines => 64, + %args + }, $class; + } + + my $ssl_verify_args = { + check_cn => "when_only", + wildcards_in_alt => "anywhere", + wildcards_in_cn => "anywhere" + }; + + sub connect { + @_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/); + my ($self, $scheme, $host, $port) = @_; + + if ( $scheme eq 'https' ) { + eval "require IO::Socket::SSL" + unless exists $INC{'IO/Socket/SSL.pm'}; + croak(qq/IO::Socket::SSL must be installed for https support\n/) + unless $INC{'IO/Socket/SSL.pm'}; + } + elsif ( $scheme ne 'http' ) { + croak(qq/Unsupported URL scheme '$scheme'/); + } + + $self->{fh} = 'IO::Socket::INET'->new( + PeerHost => $host, + PeerPort => $port, + Proto => 'tcp', + Type => SOCK_STREAM, + Timeout => $self->{timeout} + ) or croak(qq/Could not connect to '$host:$port': $@/); + + binmode($self->{fh}) + or croak(qq/Could not binmode() socket: '$!'/); + + if ( $scheme eq 'https') { + IO::Socket::SSL->start_SSL($self->{fh}); + ref($self->{fh}) eq 'IO::Socket::SSL' + or die(qq/SSL connection failed for $host\n/); + $self->{fh}->verify_hostname( $host, $ssl_verify_args ) + or die(qq/SSL certificate not valid for $host\n/); + } + + $self->{host} = $host; + $self->{port} = $port; + + return $self; + } + + sub close { + @_ == 1 || croak(q/Usage: $handle->close()/); + my ($self) = @_; + CORE::close($self->{fh}) + or croak(qq/Could not close socket: '$!'/); + } + + sub write { + @_ == 2 || croak(q/Usage: $handle->write(buf)/); + my ($self, $buf) = @_; + + if ( $] ge '5.008' ) { + utf8::downgrade($buf, 1) + or croak(q/Wide character in write()/); + } + + my $len = length $buf; + my $off = 0; + + local $SIG{PIPE} = 'IGNORE'; + + while () { + $self->can_write + or croak(q/Timed out while waiting for socket to become ready for writing/); + my $r = syswrite($self->{fh}, $buf, $len, $off); + if (defined $r) { + $len -= $r; + $off += $r; + last unless $len > 0; + } + elsif ($! == EPIPE) { + croak(qq/Socket closed by remote server: $!/); + } + elsif ($! != EINTR) { + croak(qq/Could not write to socket: '$!'/); + } + } + return $off; + } + + sub read { + @_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len [, allow_partial])/); + my ($self, $len, $allow_partial) = @_; + + my $buf = ''; + my $got = length $self->{rbuf}; + + if ($got) { + my $take = ($got < $len) ? $got : $len; + $buf = substr($self->{rbuf}, 0, $take, ''); + $len -= $take; + } + + while ($len > 0) { + $self->can_read + or croak(q/Timed out while waiting for socket to become ready for reading/); + my $r = sysread($self->{fh}, $buf, $len, length $buf); + if (defined $r) { + last unless $r; + $len -= $r; + } + elsif ($! != EINTR) { + croak(qq/Could not read from socket: '$!'/); + } + } + if ($len && !$allow_partial) { + croak(q/Unexpected end of stream/); + } + return $buf; + } + + sub readline { + @_ == 1 || croak(q/Usage: $handle->readline()/); + my ($self) = @_; + + while () { + if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) { + return $1; + } + if (length $self->{rbuf} >= $self->{max_line_size}) { + croak(qq/Line size exceeds the maximum allowed size of $self->{max_line_size}/); + } + $self->can_read + or croak(q/Timed out while waiting for socket to become ready for reading/); + my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf}); + if (defined $r) { + last unless $r; + } + elsif ($! != EINTR) { + croak(qq/Could not read from socket: '$!'/); + } + } + croak(q/Unexpected end of stream while looking for line/); + } + + sub read_header_lines { + @_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/); + my ($self, $headers) = @_; + $headers ||= {}; + my $lines = 0; + my $val; + + while () { + my $line = $self->readline; + + if (++$lines >= $self->{max_header_lines}) { + croak(qq/Header lines exceeds maximum number allowed of $self->{max_header_lines}/); + } + elsif ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) { + my ($field_name) = lc $1; + if (exists $headers->{$field_name}) { + for ($headers->{$field_name}) { + $_ = [$_] unless ref $_ eq "ARRAY"; + push @$_, $2; + $val = \$_->[-1]; + } + } + else { + $val = \($headers->{$field_name} = $2); + } + } + elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) { + $val + or croak(q/Unexpected header continuation line/); + next unless length $1; + $$val .= ' ' if length $$val; + $$val .= $1; + } + elsif ($line =~ /\A \x0D?\x0A \z/x) { + last; + } + else { + croak(q/Malformed header line: / . $Printable->($line)); + } + } + return $headers; + } + + sub write_request { + @_ == 2 || croak(q/Usage: $handle->write_request(request)/); + my($self, $request) = @_; + $self->write_request_header(@{$request}{qw/method uri headers/}); + $self->write_body($request) if $request->{cb}; + return; + } + + my %HeaderCase = ( + 'content-md5' => 'Content-MD5', + 'etag' => 'ETag', + 'te' => 'TE', + 'www-authenticate' => 'WWW-Authenticate', + 'x-xss-protection' => 'X-XSS-Protection', + ); + + sub write_header_lines { + (@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/); + my($self, $headers) = @_; + + my $buf = ''; + while (my ($k, $v) = each %$headers) { + my $field_name = lc $k; + if (exists $HeaderCase{$field_name}) { + $field_name = $HeaderCase{$field_name}; + } + else { + $field_name =~ /\A $Token+ \z/xo + or croak(q/Invalid HTTP header field name: / . $Printable->($field_name)); + $field_name =~ s/\b(\w)/\u$1/g; + $HeaderCase{lc $field_name} = $field_name; + } + for (ref $v eq 'ARRAY' ? @$v : $v) { + /[^\x0D\x0A]/ + or croak(qq/Invalid HTTP header field value ($field_name): / . $Printable->($_)); + $buf .= "$field_name: $_\x0D\x0A"; + } + } + $buf .= "\x0D\x0A"; + return $self->write($buf); + } + + sub read_body { + @_ == 3 || croak(q/Usage: $handle->read_body(callback, response)/); + my ($self, $cb, $response) = @_; + my $te = $response->{headers}{'transfer-encoding'} || ''; + if ( grep { /chunked/i } ( ref $te eq 'ARRAY' ? @$te : $te ) ) { + $self->read_chunked_body($cb, $response); + } + else { + $self->read_content_body($cb, $response); + } + return; + } + + sub write_body { + @_ == 2 || croak(q/Usage: $handle->write_body(request)/); + my ($self, $request) = @_; + if ($request->{headers}{'content-length'}) { + return $self->write_content_body($request); + } + else { + return $self->write_chunked_body($request); + } + } + + sub read_content_body { + @_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/); + my ($self, $cb, $response, $content_length) = @_; + $content_length ||= $response->{headers}{'content-length'}; + + if ( $content_length ) { + my $len = $content_length; + while ($len > 0) { + my $read = ($len > BUFSIZE) ? BUFSIZE : $len; + $cb->($self->read($read, 0), $response); + $len -= $read; + } + } + else { + my $chunk; + $cb->($chunk, $response) while length( $chunk = $self->read(BUFSIZE, 1) ); + } + + return; + } + + sub write_content_body { + @_ == 2 || croak(q/Usage: $handle->write_content_body(request)/); + my ($self, $request) = @_; + + my ($len, $content_length) = (0, $request->{headers}{'content-length'}); + while () { + my $data = $request->{cb}->(); + + defined $data && length $data + or last; + + if ( $] ge '5.008' ) { + utf8::downgrade($data, 1) + or croak(q/Wide character in write_content()/); + } + + $len += $self->write($data); + } + + $len == $content_length + or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/); + + return $len; + } + + sub read_chunked_body { + @_ == 3 || croak(q/Usage: $handle->read_chunked_body(callback, $response)/); + my ($self, $cb, $response) = @_; + + while () { + my $head = $self->readline; + + $head =~ /\A ([A-Fa-f0-9]+)/x + or croak(q/Malformed chunk head: / . $Printable->($head)); + + my $len = hex($1) + or last; + + $self->read_content_body($cb, $response, $len); + + $self->read(2) eq "\x0D\x0A" + or croak(q/Malformed chunk: missing CRLF after chunk data/); + } + $self->read_header_lines($response->{headers}); + return; + } + + sub write_chunked_body { + @_ == 2 || croak(q/Usage: $handle->write_chunked_body(request)/); + my ($self, $request) = @_; + + my $len = 0; + while () { + my $data = $request->{cb}->(); + + defined $data && length $data + or last; + + if ( $] ge '5.008' ) { + utf8::downgrade($data, 1) + or croak(q/Wide character in write_chunked_body()/); + } + + $len += length $data; + + my $chunk = sprintf '%X', length $data; + $chunk .= "\x0D\x0A"; + $chunk .= $data; + $chunk .= "\x0D\x0A"; + + $self->write($chunk); + } + $self->write("0\x0D\x0A"); + $self->write_header_lines($request->{trailer_cb}->()) + if ref $request->{trailer_cb} eq 'CODE'; + return $len; + } + + sub read_response_header { + @_ == 1 || croak(q/Usage: $handle->read_response_header()/); + my ($self) = @_; + + my $line = $self->readline; + + $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x + or croak(q/Malformed Status-Line: / . $Printable->($line)); + + my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4); + + croak (qq/Unsupported HTTP protocol: $protocol/) + unless $version =~ /0*1\.0*[01]/; + + return { + status => $status, + reason => $reason, + headers => $self->read_header_lines, + protocol => $protocol, + }; + } + + sub write_request_header { + @_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/); + my ($self, $method, $request_uri, $headers) = @_; + + return $self->write("$method $request_uri HTTP/1.1\x0D\x0A") + + $self->write_header_lines($headers); + } + + sub _do_timeout { + my ($self, $type, $timeout) = @_; + $timeout = $self->{timeout} + unless defined $timeout && $timeout >= 0; + + my $fd = fileno $self->{fh}; + defined $fd && $fd >= 0 + or croak(q/select(2): 'Bad file descriptor'/); + + my $initial = time; + my $pending = $timeout; + my $nfound; + + vec(my $fdset = '', $fd, 1) = 1; + + while () { + $nfound = ($type eq 'read') + ? select($fdset, undef, undef, $pending) + : select(undef, $fdset, undef, $pending) ; + if ($nfound == -1) { + $! == EINTR + or croak(qq/select(2): '$!'/); + redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0; + $nfound = 0; + } + last; + } + $! = 0; + return $nfound; + } + + sub can_read { + @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/); + my $self = shift; + return $self->_do_timeout('read', @_) + } + + sub can_write { + @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/); + my $self = shift; + return $self->_do_timeout('write', @_) + } + + 1; + + + + __END__ + =pod + + =head1 NAME + + HTTP::Tiny - A small, simple, correct HTTP/1.1 client + + =head1 VERSION + + version 0.009 + + =head1 SYNOPSIS + + use HTTP::Tiny; + + my $response = HTTP::Tiny->new->get('http://example.com/'); + + die "Failed!\n" unless $response->{success}; + + print "$response->{status} $response->{reason}\n"; + + while (my ($k, $v) = each %{$response->{headers}}) { + for (ref $v eq 'ARRAY' ? @$v : $v) { + print "$k: $_\n"; + } + } + + print $response->{content} if length $response->{content}; + + =head1 DESCRIPTION + + This is a very simple HTTP/1.1 client, designed primarily for doing simple GET + requests without the overhead of a large framework like L<LWP::UserAgent>. + + It is more correct and more complete than L<HTTP::Lite>. It supports + proxies (currently only non-authenticating ones) and redirection. It + also correctly resumes after EINTR. + + =head1 METHODS + + =head2 new + + $http = HTTP::Tiny->new( %attributes ); + + This constructor returns a new HTTP::Tiny object. Valid attributes include: + + =over 4 + + =item * + + agent + + A user-agent string (defaults to 'HTTP::Tiny/$VERSION') + + =item * + + default_headers + + A hashref of default headers to apply to requests + + =item * + + max_redirect + + Maximum number of redirects allowed (defaults to 5) + + =item * + + max_size + + Maximum response size (only when not using a data callback). If defined, + responses larger than this will die with an error message + + =item * + + proxy + + URL of a proxy server to use. + + =item * + + timeout + + Request timeout in seconds (default is 60) + + =back + + =head2 get + + $response = $http->get($url); + $response = $http->get($url, \%options); + + Executes a C<GET> request for the given URL. The URL must have unsafe + characters escaped and international domain names encoded. Internally, it just + calls C<request()> with 'GET' as the method. See C<request()> for valid + options and a description of the response. + + =head2 mirror + + $response = $http->mirror($url, $file, \%options) + if ( $response->{success} ) { + print "$file is up to date\n"; + } + + Executes a C<GET> request for the URL and saves the response body to the file + name provided. The URL must have unsafe characters escaped and international + domain names encoded. If the file already exists, the request will includes an + C<If-Modified-Since> header with the modification timestamp of the file. You + may specificy a different C<If-Modified-Since> header yourself in the C<< + $options->{headers} >> hash. + + The C<success> field of the response will be true if the status code is 2XX + or 304 (unmodified). + + If the file was modified and the server response includes a properly + formatted C<Last-Modified> header, the file modification time will + be updated accordingly. + + =head2 request + + $response = $http->request($method, $url); + $response = $http->request($method, $url, \%options); + + Executes an HTTP request of the given method type ('GET', 'HEAD', 'POST', + 'PUT', etc.) on the given URL. The URL must have unsafe characters escaped and + international domain names encoded. A hashref of options may be appended to + modify the request. + + Valid options are: + + =over 4 + + =item * + + headers + + A hashref containing headers to include with the request. If the value for + a header is an array reference, the header will be output multiple times with + each value in the array. These headers over-write any default headers. + + =item * + + content + + A scalar to include as the body of the request OR a code reference + that will be called iteratively to produce the body of the response + + =item * + + trailer_callback + + A code reference that will be called if it exists to provide a hashref + of trailing headers (only used with chunked transfer-encoding) + + =item * + + data_callback + + A code reference that will be called for each chunks of the response + body received. + + =back + + If the C<content> option is a code reference, it will be called iteratively + to provide the content body of the request. It should return the empty + string or undef when the iterator is exhausted. + + If the C<data_callback> option is provided, it will be called iteratively until + the entire response body is received. The first argument will be a string + containing a chunk of the response body, the second argument will be the + in-progress response hash reference, as described below. (This allows + customizing the action of the callback based on the C<status> or C<headers> + received prior to the content body.) + + The C<request> method returns a hashref containing the response. The hashref + will have the following keys: + + =over 4 + + =item * + + success + + Boolean indicating whether the operation returned a 2XX status code + + =item * + + status + + The HTTP status code of the response + + =item * + + reason + + The response phrase returned by the server + + =item * + + content + + The body of the response. If the response does not have any content + or if a data callback is provided to consume the response body, + this will be the empty string + + =item * + + headers + + A hashref of header fields. All header field names will be normalized + to be lower case. If a header is repeated, the value will be an arrayref; + it will otherwise be a scalar string containing the value + + =back + + On an exception during the execution of the request, the C<status> field will + contain 599, and the C<content> field will contain the text of the exception. + + =for Pod::Coverage agent + default_headers + max_redirect + max_size + proxy + timeout + + =head1 LIMITATIONS + + HTTP::Tiny is I<conditionally compliant> with the + L<HTTP/1.1 specification|http://www.w3.org/Protocols/rfc2616/rfc2616.html>. + It attempts to meet all "MUST" requirements of the specification, but does not + implement all "SHOULD" requirements. + + Some particular limitations of note include: + + =over + + =item * + + HTTP::Tiny focuses on correct transport. Users are responsible for ensuring + that user-defined headers and content are compliant with the HTTP/1.1 + specification. + + =item * + + Users must ensure that URLs are properly escaped for unsafe characters and that + international domain names are properly encoded to ASCII. See L<URI::Escape>, + L<URI::_punycode> and L<Net::IDN::Encode>. + + =item * + + Redirection is very strict against the specification. Redirection is only + automatic for response codes 301, 302 and 307 if the request method is 'GET' or + 'HEAD'. Response code 303 is always converted into a 'GET' redirection, as + mandated by the specification. There is no automatic support for status 305 + ("Use proxy") redirections. + + =item * + + Persistant connections are not supported. The C<Connection> header will + always be set to C<close>. + + =item * + + Direct C<https> connections are supported only if L<IO::Socket::SSL> is + installed. There is no support for C<https> connections via proxy. + + =item * + + Cookies are not directly supported. Users that set a C<Cookie> header + should also set C<max_redirect> to zero to ensure cookies are not + inappropriately re-transmitted. + + =item * + + Proxy environment variables are not supported. + + =item * + + There is no provision for delaying a request body using an C<Expect> header. + Unexpected C<1XX> responses are silently ignored as per the specification. + + =item * + + Only 'chunked' C<Transfer-Encoding> is supported. + + =item * + + There is no support for a Request-URI of '*' for the 'OPTIONS' request. + + =back + + =head1 SEE ALSO + + =over 4 + + =item * + + L<LWP::UserAgent> + + =back + + =head1 AUTHORS + + =over 4 + + =item * + + Christian Hansen <chansen@cpan.org> + + =item * + + David Golden <dagolden@cpan.org> + + =back + + =head1 COPYRIGHT AND LICENSE + + This software is copyright (c) 2011 by Christian Hansen. + + This is free software; you can redistribute it and/or modify it under + the same terms as the Perl 5 programming language system itself. + + =cut + +HTTP_TINY + +$fatpacked{"Module/Metadata.pm"} = <<'MODULE_METADATA'; + # -*- mode: cperl; tab-width: 8; indent-tabs-mode: nil; basic-offset: 2 -*- + # vim:ts=8:sw=2:et:sta:sts=2 + package Module::Metadata; + + # Adapted from Perl-licensed code originally distributed with + # Module-Build by Ken Williams + + # This module provides routines to gather information about + # perl modules (assuming this may be expanded in the distant + # parrot future to look at other types of modules). + + use strict; + use vars qw($VERSION); + $VERSION = '1.000003'; + $VERSION = eval $VERSION; + + use File::Spec; + use IO::File; + use version 0.87; + BEGIN { + if ($INC{'Log/Contextual.pm'}) { + Log::Contextual->import('log_info'); + } else { + *log_info = sub (&) { warn $_[0]->() }; + } + } + use File::Find qw(find); + + my $V_NUM_REGEXP = qr{v?[0-9._]+}; # crudely, a v-string or decimal + + my $PKG_REGEXP = qr{ # match a package declaration + ^[\s\{;]* # intro chars on a line + package # the word 'package' + \s+ # whitespace + ([\w:]+) # a package name + \s* # optional whitespace + ($V_NUM_REGEXP)? # optional version number + \s* # optional whitesapce + ; # semicolon line terminator + }x; + + my $VARNAME_REGEXP = qr{ # match fully-qualified VERSION name + ([\$*]) # sigil - $ or * + ( + ( # optional leading package name + (?:::|\')? # possibly starting like just :: (Ì la $::VERSION) + (?:\w+(?:::|\'))* # Foo::Bar:: ... + )? + VERSION + )\b + }x; + + my $VERS_REGEXP = qr{ # match a VERSION definition + (?: + \(\s*$VARNAME_REGEXP\s*\) # with parens + | + $VARNAME_REGEXP # without parens + ) + \s* + =[^=~] # = but not ==, nor =~ + }x; + + + sub new_from_file { + my $class = shift; + my $filename = File::Spec->rel2abs( shift ); + + return undef unless defined( $filename ) && -f $filename; + return $class->_init(undef, $filename, @_); + } + + sub new_from_module { + my $class = shift; + my $module = shift; + my %props = @_; + + $props{inc} ||= \@INC; + my $filename = $class->find_module_by_name( $module, $props{inc} ); + return undef unless defined( $filename ) && -f $filename; + return $class->_init($module, $filename, %props); + } + + { + + my $compare_versions = sub { + my ($v1, $op, $v2) = @_; + $v1 = version->new($v1) + unless UNIVERSAL::isa($v1,'version'); + + my $eval_str = "\$v1 $op \$v2"; + my $result = eval $eval_str; + log_info { "error comparing versions: '$eval_str' $@" } if $@; + + return $result; + }; + + my $normalize_version = sub { + my ($version) = @_; + if ( $version =~ /[=<>!,]/ ) { # logic, not just version + # take as is without modification + } + elsif ( ref $version eq 'version' ) { # version objects + $version = $version->is_qv ? $version->normal : $version->stringify; + } + elsif ( $version =~ /^[^v][^.]*\.[^.]+\./ ) { # no leading v, multiple dots + # normalize string tuples without "v": "1.2.3" -> "v1.2.3" + $version = "v$version"; + } + else { + # leave alone + } + return $version; + }; + + # separate out some of the conflict resolution logic + + my $resolve_module_versions = sub { + my $packages = shift; + + my( $file, $version ); + my $err = ''; + foreach my $p ( @$packages ) { + if ( defined( $p->{version} ) ) { + if ( defined( $version ) ) { + if ( $compare_versions->( $version, '!=', $p->{version} ) ) { + $err .= " $p->{file} ($p->{version})\n"; + } else { + # same version declared multiple times, ignore + } + } else { + $file = $p->{file}; + $version = $p->{version}; + } + } + $file ||= $p->{file} if defined( $p->{file} ); + } + + if ( $err ) { + $err = " $file ($version)\n" . $err; + } + + my %result = ( + file => $file, + version => $version, + err => $err + ); + + return \%result; + }; + + sub package_versions_from_directory { + my ( $class, $dir, $files ) = @_; + + my @files; + + if ( $files ) { + @files = @$files; + } else { + find( { + wanted => sub { + push @files, $_ if -f $_ && /\.pm$/; + }, + no_chdir => 1, + }, $dir ); + } + + # First, we enumerate all packages & versions, + # separating into primary & alternative candidates + my( %prime, %alt ); + foreach my $file (@files) { + my $mapped_filename = File::Spec->abs2rel( $file, $dir ); + my @path = split( /\//, $mapped_filename ); + (my $prime_package = join( '::', @path )) =~ s/\.pm$//; + + my $pm_info = $class->new_from_file( $file ); + + foreach my $package ( $pm_info->packages_inside ) { + next if $package eq 'main'; # main can appear numerous times, ignore + next if $package eq 'DB'; # special debugging package, ignore + next if grep /^_/, split( /::/, $package ); # private package, ignore + + my $version = $pm_info->version( $package ); + + if ( $package eq $prime_package ) { + if ( exists( $prime{$package} ) ) { + # M::B::ModuleInfo will handle this conflict + die "Unexpected conflict in '$package'; multiple versions found.\n"; + } else { + $prime{$package}{file} = $mapped_filename; + $prime{$package}{version} = $version if defined( $version ); + } + } else { + push( @{$alt{$package}}, { + file => $mapped_filename, + version => $version, + } ); + } + } + } + + # Then we iterate over all the packages found above, identifying conflicts + # and selecting the "best" candidate for recording the file & version + # for each package. + foreach my $package ( keys( %alt ) ) { + my $result = $resolve_module_versions->( $alt{$package} ); + + if ( exists( $prime{$package} ) ) { # primary package selected + + if ( $result->{err} ) { + # Use the selected primary package, but there are conflicting + # errors among multiple alternative packages that need to be + # reported + log_info { + "Found conflicting versions for package '$package'\n" . + " $prime{$package}{file} ($prime{$package}{version})\n" . + $result->{err} + }; + + } elsif ( defined( $result->{version} ) ) { + # There is a primary package selected, and exactly one + # alternative package + + if ( exists( $prime{$package}{version} ) && + defined( $prime{$package}{version} ) ) { + # Unless the version of the primary package agrees with the + # version of the alternative package, report a conflict + if ( $compare_versions->( + $prime{$package}{version}, '!=', $result->{version} + ) + ) { + + log_info { + "Found conflicting versions for package '$package'\n" . + " $prime{$package}{file} ($prime{$package}{version})\n" . + " $result->{file} ($result->{version})\n" + }; + } + + } else { + # The prime package selected has no version so, we choose to + # use any alternative package that does have a version + $prime{$package}{file} = $result->{file}; + $prime{$package}{version} = $result->{version}; + } + + } else { + # no alt package found with a version, but we have a prime + # package so we use it whether it has a version or not + } + + } else { # No primary package was selected, use the best alternative + + if ( $result->{err} ) { + log_info { + "Found conflicting versions for package '$package'\n" . + $result->{err} + }; + } + + # Despite possible conflicting versions, we choose to record + # something rather than nothing + $prime{$package}{file} = $result->{file}; + $prime{$package}{version} = $result->{version} + if defined( $result->{version} ); + } + } + + # Normalize versions. Can't use exists() here because of bug in YAML::Node. + # XXX "bug in YAML::Node" comment seems irrelvant -- dagolden, 2009-05-18 + for (grep defined $_->{version}, values %prime) { + $_->{version} = $normalize_version->( $_->{version} ); + } + + return \%prime; + } + } + + + sub _init { + my $class = shift; + my $module = shift; + my $filename = shift; + my %props = @_; + + my( %valid_props, @valid_props ); + @valid_props = qw( collect_pod inc ); + @valid_props{@valid_props} = delete( @props{@valid_props} ); + warn "Unknown properties: @{[keys %props]}\n" if scalar( %props ); + + my %data = ( + module => $module, + filename => $filename, + version => undef, + packages => [], + versions => {}, + pod => {}, + pod_headings => [], + collect_pod => 0, + + %valid_props, + ); + + my $self = bless(\%data, $class); + + $self->_parse_file(); + + unless($self->{module} and length($self->{module})) { + my ($v, $d, $f) = File::Spec->splitpath($self->{filename}); + if($f =~ /\.pm$/) { + $f =~ s/\..+$//; + my @candidates = grep /$f$/, @{$self->{packages}}; + $self->{module} = shift(@candidates); # punt + } + else { + if(grep /main/, @{$self->{packages}}) { + $self->{module} = 'main'; + } + else { + $self->{module} = $self->{packages}[0] || ''; + } + } + } + + $self->{version} = $self->{versions}{$self->{module}} + if defined( $self->{module} ); + + return $self; + } + + # class method + sub _do_find_module { + my $class = shift; + my $module = shift || die 'find_module_by_name() requires a package name'; + my $dirs = shift || \@INC; + + my $file = File::Spec->catfile(split( /::/, $module)); + foreach my $dir ( @$dirs ) { + my $testfile = File::Spec->catfile($dir, $file); + return [ File::Spec->rel2abs( $testfile ), $dir ] + if -e $testfile and !-d _; # For stuff like ExtUtils::xsubpp + return [ File::Spec->rel2abs( "$testfile.pm" ), $dir ] + if -e "$testfile.pm"; + } + return; + } + + # class method + sub find_module_by_name { + my $found = shift()->_do_find_module(@_) or return; + return $found->[0]; + } + + # class method + sub find_module_dir_by_name { + my $found = shift()->_do_find_module(@_) or return; + return $found->[1]; + } + + + # given a line of perl code, attempt to parse it if it looks like a + # $VERSION assignment, returning sigil, full name, & package name + sub _parse_version_expression { + my $self = shift; + my $line = shift; + + my( $sig, $var, $pkg ); + if ( $line =~ $VERS_REGEXP ) { + ( $sig, $var, $pkg ) = $2 ? ( $1, $2, $3 ) : ( $4, $5, $6 ); + if ( $pkg ) { + $pkg = ($pkg eq '::') ? 'main' : $pkg; + $pkg =~ s/::$//; + } + } + + return ( $sig, $var, $pkg ); + } + + sub _parse_file { + my $self = shift; + + my $filename = $self->{filename}; + my $fh = IO::File->new( $filename ) + or die( "Can't open '$filename': $!" ); + + $self->_parse_fh($fh); + } + + sub _parse_fh { + my ($self, $fh) = @_; + + my( $in_pod, $seen_end, $need_vers ) = ( 0, 0, 0 ); + my( @pkgs, %vers, %pod, @pod ); + my $pkg = 'main'; + my $pod_sect = ''; + my $pod_data = ''; + + while (defined( my $line = <$fh> )) { + my $line_num = $.; + + chomp( $line ); + next if $line =~ /^\s*#/; + + $in_pod = ($line =~ /^=(?!cut)/) ? 1 : ($line =~ /^=cut/) ? 0 : $in_pod; + + # Would be nice if we could also check $in_string or something too + last if !$in_pod && $line =~ /^__(?:DATA|END)__$/; + + if ( $in_pod || $line =~ /^=cut/ ) { + + if ( $line =~ /^=head\d\s+(.+)\s*$/ ) { + push( @pod, $1 ); + if ( $self->{collect_pod} && length( $pod_data ) ) { + $pod{$pod_sect} = $pod_data; + $pod_data = ''; + } + $pod_sect = $1; + + + } elsif ( $self->{collect_pod} ) { + $pod_data .= "$line\n"; + + } + + } else { + + $pod_sect = ''; + $pod_data = ''; + + # parse $line to see if it's a $VERSION declaration + my( $vers_sig, $vers_fullname, $vers_pkg ) = + $self->_parse_version_expression( $line ); + + if ( $line =~ $PKG_REGEXP ) { + $pkg = $1; + push( @pkgs, $pkg ) unless grep( $pkg eq $_, @pkgs ); + $vers{$pkg} = (defined $2 ? $2 : undef) unless exists( $vers{$pkg} ); + $need_vers = defined $2 ? 0 : 1; + + # VERSION defined with full package spec, i.e. $Module::VERSION + } elsif ( $vers_fullname && $vers_pkg ) { + push( @pkgs, $vers_pkg ) unless grep( $vers_pkg eq $_, @pkgs ); + $need_vers = 0 if $vers_pkg eq $pkg; + + unless ( defined $vers{$vers_pkg} && length $vers{$vers_pkg} ) { + $vers{$vers_pkg} = + $self->_evaluate_version_line( $vers_sig, $vers_fullname, $line ); + } else { + # Warn unless the user is using the "$VERSION = eval + # $VERSION" idiom (though there are probably other idioms + # that we should watch out for...) + warn <<"EOM" unless $line =~ /=\s*eval/; + Package '$vers_pkg' already declared with version '$vers{$vers_pkg}', + ignoring subsequent declaration on line $line_num. + EOM + } + + # first non-comment line in undeclared package main is VERSION + } elsif ( !exists($vers{main}) && $pkg eq 'main' && $vers_fullname ) { + $need_vers = 0; + my $v = + $self->_evaluate_version_line( $vers_sig, $vers_fullname, $line ); + $vers{$pkg} = $v; + push( @pkgs, 'main' ); + + # first non-comment line in undeclared package defines package main + } elsif ( !exists($vers{main}) && $pkg eq 'main' && $line =~ /\w+/ ) { + $need_vers = 1; + $vers{main} = ''; + push( @pkgs, 'main' ); + + # only keep if this is the first $VERSION seen + } elsif ( $vers_fullname && $need_vers ) { + $need_vers = 0; + my $v = + $self->_evaluate_version_line( $vers_sig, $vers_fullname, $line ); + + + unless ( defined $vers{$pkg} && length $vers{$pkg} ) { + $vers{$pkg} = $v; + } else { + warn <<"EOM"; + Package '$pkg' already declared with version '$vers{$pkg}' + ignoring new version '$v' on line $line_num. + EOM + } + + } + + } + + } + + if ( $self->{collect_pod} && length($pod_data) ) { + $pod{$pod_sect} = $pod_data; + } + + $self->{versions} = \%vers; + $self->{packages} = \@pkgs; + $self->{pod} = \%pod; + $self->{pod_headings} = \@pod; + } + + { + my $pn = 0; + sub _evaluate_version_line { + my $self = shift; + my( $sigil, $var, $line ) = @_; + + # Some of this code came from the ExtUtils:: hierarchy. + + # We compile into $vsub because 'use version' would cause + # compiletime/runtime issues with local() + my $vsub; + $pn++; # everybody gets their own package + my $eval = qq{BEGIN { q# Hide from _packages_inside() + #; package Module::Metadata::_version::p$pn; + use version; + no strict; + + local $sigil$var; + \$$var=undef; + \$vsub = sub { + $line; + \$$var + }; + }}; + + local $^W; + # Try to get the $VERSION + eval $eval; + # some modules say $VERSION = $Foo::Bar::VERSION, but Foo::Bar isn't + # installed, so we need to hunt in ./lib for it + if ( $@ =~ /Can't locate/ && -d 'lib' ) { + local @INC = ('lib',@INC); + eval $eval; + } + warn "Error evaling version line '$eval' in $self->{filename}: $@\n" + if $@; + (ref($vsub) eq 'CODE') or + die "failed to build version sub for $self->{filename}"; + my $result = eval { $vsub->() }; + die "Could not get version from $self->{filename} by executing:\n$eval\n\nThe fatal error was: $@\n" + if $@; + + # Upgrade it into a version object + my $version = eval { _dwim_version($result) }; + + die "Version '$result' from $self->{filename} does not appear to be valid:\n$eval\n\nThe fatal error was: $@\n" + unless defined $version; # "0" is OK! + + return $version; + } + } + + # Try to DWIM when things fail the lax version test in obvious ways + { + my @version_prep = ( + # Best case, it just works + sub { return shift }, + + # If we still don't have a version, try stripping any + # trailing junk that is prohibited by lax rules + sub { + my $v = shift; + $v =~ s{([0-9])[a-z-].*$}{$1}i; # 1.23-alpha or 1.23b + return $v; + }, + + # Activestate apparently creates custom versions like '1.23_45_01', which + # cause version.pm to think it's an invalid alpha. So check for that + # and strip them + sub { + my $v = shift; + my $num_dots = () = $v =~ m{(\.)}g; + my $num_unders = () = $v =~ m{(_)}g; + my $leading_v = substr($v,0,1) eq 'v'; + if ( ! $leading_v && $num_dots < 2 && $num_unders > 1 ) { + $v =~ s{_}{}g; + $num_unders = () = $v =~ m{(_)}g; + } + return $v; + }, + + # Worst case, try numifying it like we would have before version objects + sub { + my $v = shift; + no warnings 'numeric'; + return 0 + $v; + }, + + ); + + sub _dwim_version { + my ($result) = shift; + + return $result if ref($result) eq 'version'; + + my ($version, $error); + for my $f (@version_prep) { + $result = $f->($result); + $version = eval { version->new($result) }; + $error ||= $@ if $@; # capture first failure + last if defined $version; + } + + die $error unless defined $version; + + return $version; + } + } + + ############################################################ + + # accessors + sub name { $_[0]->{module} } + + sub filename { $_[0]->{filename} } + sub packages_inside { @{$_[0]->{packages}} } + sub pod_inside { @{$_[0]->{pod_headings}} } + sub contains_pod { $#{$_[0]->{pod_headings}} } + + sub version { + my $self = shift; + my $mod = shift || $self->{module}; + my $vers; + if ( defined( $mod ) && length( $mod ) && + exists( $self->{versions}{$mod} ) ) { + return $self->{versions}{$mod}; + } else { + return undef; + } + } + + sub pod { + my $self = shift; + my $sect = shift; + if ( defined( $sect ) && length( $sect ) && + exists( $self->{pod}{$sect} ) ) { + return $self->{pod}{$sect}; + } else { + return undef; + } + } + + 1; + + =head1 NAME + + Module::Metadata - Gather package and POD information from perl module files + + =head1 DESCRIPTION + + =over 4 + + =item new_from_file($filename, collect_pod => 1) + + Construct a C<ModuleInfo> object given the path to a file. Takes an optional + argument C<collect_pod> which is a boolean that determines whether + POD data is collected and stored for reference. POD data is not + collected by default. POD headings are always collected. + + =item new_from_module($module, collect_pod => 1, inc => \@dirs) + + Construct a C<ModuleInfo> object given a module or package name. In addition + to accepting the C<collect_pod> argument as described above, this + method accepts a C<inc> argument which is a reference to an array of + of directories to search for the module. If none are given, the + default is @INC. + + =item name() + + Returns the name of the package represented by this module. If there + are more than one packages, it makes a best guess based on the + filename. If it's a script (i.e. not a *.pm) the package name is + 'main'. + + =item version($package) + + Returns the version as defined by the $VERSION variable for the + package as returned by the C<name> method if no arguments are + given. If given the name of a package it will attempt to return the + version of that package if it is specified in the file. + + =item filename() + + Returns the absolute path to the file. + + =item packages_inside() + + Returns a list of packages. + + =item pod_inside() + + Returns a list of POD sections. + + =item contains_pod() + + Returns true if there is any POD in the file. + + =item pod($section) + + Returns the POD data in the given section. + + =item find_module_by_name($module, \@dirs) + + Returns the path to a module given the module or package name. A list + of directories can be passed in as an optional parameter, otherwise + @INC is searched. + + Can be called as either an object or a class method. + + =item find_module_dir_by_name($module, \@dirs) + + Returns the entry in C<@dirs> (or C<@INC> by default) that contains + the module C<$module>. A list of directories can be passed in as an + optional parameter, otherwise @INC is searched. + + Can be called as either an object or a class method. + + =item package_versions_from_directory($dir, \@files?) + + Scans C<$dir> for .pm files (unless C<@files> is given, in which case looks + for those files in C<$dir> - and reads each file for packages and versions, + returning a hashref of the form: + + { + 'Package::Name' => { + version => '0.123', + file => 'Package/Name.pm' + }, + 'OtherPackage::Name' => ... + } + + =item log_info (internal) + + Used internally to perform logging; imported from Log::Contextual if + Log::Contextual has already been loaded, otherwise simply calls warn. + + =back + + =head1 AUTHOR + + Ken Williams <kwilliams@cpan.org>, Randy W. Sims <RandyS@ThePierianSpring.org> + + Released as Module::Metadata by Matt S Trout (mst) <mst@shadowcat.co.uk> with + assistance from David Golden (xdg) <dagolden@cpan.org> + + =head1 COPYRIGHT + + Copyright (c) 2001-2011 Ken Williams. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the same terms as Perl itself. + + =cut + +MODULE_METADATA + +$fatpacked{"Parse/CPAN/Meta.pm"} = <<'PARSE_CPAN_META'; + package Parse::CPAN::Meta;
+
+ use strict;
+ use Carp 'croak';
+
+ # UTF Support?
+ sub HAVE_UTF8 () { $] >= 5.007003 }
+ BEGIN {
+ if ( HAVE_UTF8 ) {
+ # The string eval helps hide this from Test::MinimumVersion
+ eval "require utf8;";
+ die "Failed to load UTF-8 support" if $@;
+ }
+
+ # Class structure
+ require 5.004;
+ require Exporter;
+ $Parse::CPAN::Meta::VERSION = '1.40';
+ @Parse::CPAN::Meta::ISA = qw{ Exporter };
+ @Parse::CPAN::Meta::EXPORT_OK = qw{ Load LoadFile };
+ }
+
+ # Prototypes
+ sub LoadFile ($);
+ sub Load ($);
+ sub _scalar ($$$);
+ sub _array ($$$);
+ sub _hash ($$$);
+
+ # Printable characters for escapes
+ my %UNESCAPES = (
+ z => "\x00", a => "\x07", t => "\x09",
+ n => "\x0a", v => "\x0b", f => "\x0c",
+ r => "\x0d", e => "\x1b", '\\' => '\\',
+ );
+
+
+
+
+
+ #####################################################################
+ # Implementation
+
+ # Create an object from a file
+ sub LoadFile ($) {
+ # Check the file
+ my $file = shift;
+ croak('You did not specify a file name') unless $file;
+ croak( "File '$file' does not exist" ) unless -e $file;
+ croak( "'$file' is a directory, not a file" ) unless -f _;
+ croak( "Insufficient permissions to read '$file'" ) unless -r _;
+
+ # Slurp in the file
+ local $/ = undef;
+ local *CFG;
+ unless ( open( CFG, $file ) ) {
+ croak("Failed to open file '$file': $!");
+ }
+ my $yaml = <CFG>;
+ unless ( close(CFG) ) {
+ croak("Failed to close file '$file': $!");
+ }
+
+ # Hand off to the actual parser
+ Load( $yaml );
+ }
+
+ # Parse a document from a string.
+ # Doing checks on $_[0] prevents us having to do a string copy.
+ sub Load ($) {
+ my $string = $_[0];
+ unless ( defined $string ) {
+ croak("Did not provide a string to load");
+ }
+
+ # Byte order marks
+ if ( $string =~ /^(?:\376\377|\377\376|\377\376\0\0|\0\0\376\377)/ ) {
+ croak("Stream has a non UTF-8 Unicode Byte Order Mark");
+ } else {
+ # Strip UTF-8 bom if found, we'll just ignore it
+ $string =~ s/^\357\273\277//;
+ }
+
+ # Try to decode as utf8
+ utf8::decode($string) if HAVE_UTF8;
+
+ # Check for some special cases
+ return () unless length $string;
+ unless ( $string =~ /[\012\015]+\z/ ) {
+ croak("Stream does not end with newline character");
+ }
+
+ # Split the file into lines
+ my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
+ split /(?:\015{1,2}\012|\015|\012)/, $string;
+
+ # Strip the initial YAML header
+ @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
+
+ # A nibbling parser
+ my @documents = ();
+ while ( @lines ) {
+ # Do we have a document header?
+ if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
+ # Handle scalar documents
+ shift @lines;
+ if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
+ push @documents, _scalar( "$1", [ undef ], \@lines );
+ next;
+ }
+ }
+
+ if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
+ # A naked document
+ push @documents, undef;
+ while ( @lines and $lines[0] !~ /^---/ ) {
+ shift @lines;
+ }
+
+ } elsif ( $lines[0] =~ /^\s*\-/ ) {
+ # An array at the root
+ my $document = [ ];
+ push @documents, $document;
+ _array( $document, [ 0 ], \@lines );
+
+ } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
+ # A hash at the root
+ my $document = { };
+ push @documents, $document;
+ _hash( $document, [ length($1) ], \@lines );
+
+ } else {
+ croak("Parse::CPAN::Meta failed to classify line '$lines[0]'");
+ }
+ }
+
+ if ( wantarray ) {
+ return @documents;
+ } else {
+ return $documents[-1];
+ }
+ }
+
+ # Deparse a scalar string to the actual scalar
+ sub _scalar ($$$) {
+ my ($string, $indent, $lines) = @_;
+
+ # Trim trailing whitespace
+ $string =~ s/\s*\z//;
+
+ # Explitic null/undef
+ return undef if $string eq '~';
+
+ # Quotes
+ if ( $string =~ /^\'(.*?)\'\z/ ) {
+ return '' unless defined $1;
+ $string = $1;
+ $string =~ s/\'\'/\'/g;
+ return $string;
+ }
+ if ( $string =~ /^\"((?:\\.|[^\"])*)\"\z/ ) {
+ # Reusing the variable is a little ugly,
+ # but avoids a new variable and a string copy.
+ $string = $1;
+ $string =~ s/\\"/"/g;
+ $string =~ s/\\([never\\fartz]|x([0-9a-fA-F]{2}))/(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}/gex;
+ return $string;
+ }
+
+ # Special cases
+ if ( $string =~ /^[\'\"!&]/ ) {
+ croak("Parse::CPAN::Meta does not support a feature in line '$lines->[0]'");
+ }
+ return {} if $string eq '{}';
+ return [] if $string eq '[]';
+
+ # Regular unquoted string
+ return $string unless $string =~ /^[>|]/;
+
+ # Error
+ croak("Parse::CPAN::Meta failed to find multi-line scalar content") unless @$lines;
+
+ # Check the indent depth
+ $lines->[0] =~ /^(\s*)/;
+ $indent->[-1] = length("$1");
+ if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
+ croak("Parse::CPAN::Meta found bad indenting in line '$lines->[0]'");
+ }
+
+ # Pull the lines
+ my @multiline = ();
+ while ( @$lines ) {
+ $lines->[0] =~ /^(\s*)/;
+ last unless length($1) >= $indent->[-1];
+ push @multiline, substr(shift(@$lines), length($1));
+ }
+
+ my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
+ my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
+ return join( $j, @multiline ) . $t;
+ }
+
+ # Parse an array
+ sub _array ($$$) {
+ my ($array, $indent, $lines) = @_;
+
+ while ( @$lines ) {
+ # Check for a new document
+ if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
+ while ( @$lines and $lines->[0] !~ /^---/ ) {
+ shift @$lines;
+ }
+ return 1;
+ }
+
+ # Check the indent level
+ $lines->[0] =~ /^(\s*)/;
+ if ( length($1) < $indent->[-1] ) {
+ return 1;
+ } elsif ( length($1) > $indent->[-1] ) {
+ croak("Parse::CPAN::Meta found bad indenting in line '$lines->[0]'");
+ }
+
+ if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
+ # Inline nested hash
+ my $indent2 = length("$1");
+ $lines->[0] =~ s/-/ /;
+ push @$array, { };
+ _hash( $array->[-1], [ @$indent, $indent2 ], $lines );
+
+ } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
+ # Array entry with a value
+ shift @$lines;
+ push @$array, _scalar( "$2", [ @$indent, undef ], $lines );
+
+ } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
+ shift @$lines;
+ unless ( @$lines ) {
+ push @$array, undef;
+ return 1;
+ }
+ if ( $lines->[0] =~ /^(\s*)\-/ ) {
+ my $indent2 = length("$1");
+ if ( $indent->[-1] == $indent2 ) {
+ # Null array entry
+ push @$array, undef;
+ } else {
+ # Naked indenter
+ push @$array, [ ];
+ _array( $array->[-1], [ @$indent, $indent2 ], $lines );
+ }
+
+ } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
+ push @$array, { };
+ _hash( $array->[-1], [ @$indent, length("$1") ], $lines );
+
+ } else {
+ croak("Parse::CPAN::Meta failed to classify line '$lines->[0]'");
+ }
+
+ } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) {
+ # This is probably a structure like the following...
+ # ---
+ # foo:
+ # - list
+ # bar: value
+ #
+ # ... so lets return and let the hash parser handle it
+ return 1;
+
+ } else {
+ croak("Parse::CPAN::Meta failed to classify line '$lines->[0]'");
+ }
+ }
+
+ return 1;
+ }
+
+ # Parse an array
+ sub _hash ($$$) {
+ my ($hash, $indent, $lines) = @_;
+
+ while ( @$lines ) {
+ # Check for a new document
+ if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
+ while ( @$lines and $lines->[0] !~ /^---/ ) {
+ shift @$lines;
+ }
+ return 1;
+ }
+
+ # Check the indent level
+ $lines->[0] =~ /^(\s*)/;
+ if ( length($1) < $indent->[-1] ) {
+ return 1;
+ } elsif ( length($1) > $indent->[-1] ) {
+ croak("Parse::CPAN::Meta found bad indenting in line '$lines->[0]'");
+ }
+
+ # Get the key
+ unless ( $lines->[0] =~ s/^\s*([^\'\" ][^\n]*?)\s*:(\s+|$)// ) {
+ if ( $lines->[0] =~ /^\s*[?\'\"]/ ) {
+ croak("Parse::CPAN::Meta does not support a feature in line '$lines->[0]'");
+ }
+ croak("Parse::CPAN::Meta failed to classify line '$lines->[0]'");
+ }
+ my $key = $1;
+
+ # Do we have a value?
+ if ( length $lines->[0] ) {
+ # Yes
+ $hash->{$key} = _scalar( shift(@$lines), [ @$indent, undef ], $lines );
+ } else {
+ # An indent
+ shift @$lines;
+ unless ( @$lines ) {
+ $hash->{$key} = undef;
+ return 1;
+ }
+ if ( $lines->[0] =~ /^(\s*)-/ ) {
+ $hash->{$key} = [];
+ _array( $hash->{$key}, [ @$indent, length($1) ], $lines );
+ } elsif ( $lines->[0] =~ /^(\s*)./ ) {
+ my $indent2 = length("$1");
+ if ( $indent->[-1] >= $indent2 ) {
+ # Null hash entry
+ $hash->{$key} = undef;
+ } else {
+ $hash->{$key} = {};
+ _hash( $hash->{$key}, [ @$indent, length($1) ], $lines );
+ }
+ }
+ }
+ }
+
+ return 1;
+ }
+
+ 1;
+
+ __END__
+
+ =pod
+
+ =head1 NAME
+
+ Parse::CPAN::Meta - Parse META.yml and other similar CPAN metadata files
+
+ =head1 SYNOPSIS
+
+ #############################################
+ # In your file
+
+ ---
+ rootproperty: blah
+ section:
+ one: two
+ three: four
+ Foo: Bar
+ empty: ~
+
+
+
+ #############################################
+ # In your program
+
+ use Parse::CPAN::Meta;
+
+ # Create a YAML file
+ my @yaml = Parse::CPAN::Meta::LoadFile( 'Meta.yml' );
+
+ # Reading properties
+ my $root = $yaml[0]->{rootproperty};
+ my $one = $yaml[0]->{section}->{one};
+ my $Foo = $yaml[0]->{section}->{Foo};
+
+ =head1 DESCRIPTION
+
+ B<Parse::CPAN::Meta> is a parser for F<META.yml> files, based on the
+ parser half of L<YAML::Tiny>.
+
+ It supports a basic subset of the full YAML specification, enough to
+ implement parsing of typical F<META.yml> files, and other similarly simple
+ YAML files.
+
+ If you need something with more power, move up to a full YAML parser such
+ as L<YAML>, L<YAML::Syck> or L<YAML::LibYAML>.
+
+ B<Parse::CPAN::Meta> provides a very simply API of only two functions,
+ based on the YAML functions of the same name. Wherever possible,
+ identical calling semantics are used.
+
+ All error reporting is done with exceptions (die'ing).
+
+ =head1 FUNCTIONS
+
+ For maintenance clarity, no functions are exported.
+
+ =head2 Load
+
+ my @yaml = Load( $string );
+
+ Parses a string containing a valid YAML stream into a list of Perl data
+ structures.
+
+ =head2 LoadFile
+
+ my @yaml = LoadFile( 'META.yml' );
+
+ Reads the YAML stream from a file instead of a string.
+
+ =head1 SUPPORT
+
+ Bugs should be reported via the CPAN bug tracker at
+
+ L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Parse-CPAN-Meta>
+
+ =head1 AUTHOR
+
+ Adam Kennedy E<lt>adamk@cpan.orgE<gt>
+
+ =head1 SEE ALSO
+
+ L<YAML>, L<YAML::Syck>, L<Config::Tiny>, L<CSS::Tiny>,
+ L<http://use.perl.org/~Alias/journal/29427>, L<http://ali.as/>
+
+ =head1 COPYRIGHT
+
+ Copyright 2006 - 2009 Adam Kennedy.
+
+ This program is free software; you can redistribute
+ it and/or modify it under the same terms as Perl itself.
+
+ The full text of the license can be found in the
+ LICENSE file included with this module.
+
+ =cut
+PARSE_CPAN_META + +$fatpacked{"lib/core/only.pm"} = <<'LIB_CORE_ONLY'; + package lib::core::only; + + use strict; + use warnings FATAL => 'all'; + use Config; + + sub import { + @INC = @Config{qw(privlibexp archlibexp)}; + return + } + + =head1 NAME + + lib::core::only - Remove all non-core paths from @INC to avoid site/vendor dirs + + =head1 SYNOPSIS + + use lib::core::only; # now @INC contains only the two core directories + + To get only the core directories plus the ones for the local::lib in scope: + + $ perl -Mlib::core::only -Mlocal::lib=~/perl5 myscript.pl + + To attempt to do a self-contained build (but note this will not reliably + propagate into subprocesses, see the CAVEATS below): + + $ PERL5OPT='-Mlib::core::only -Mlocal::lib=~/perl5' cpan + + =head1 DESCRIPTION + + lib::core::only is simply a shortcut to say "please reduce my @INC to only + the core lib and archlib (architecture-specific lib) directories of this perl". + + You might want to do this to ensure a local::lib contains only the code you + need, or to test an L<App::FatPacker|App::FatPacker> tree, or to avoid known + bad vendor packages. + + You might want to use this to try and install a self-contained tree of perl + modules. Be warned that that probably won't work (see L</CAVEATS>). + + This module was extracted from L<local::lib|local::lib>'s --self-contained + feature, and contains the only part that ever worked. I apologise to anybody + who thought anything else did. + + =head1 CAVEATS + + This does B<not> propagate properly across perl invocations like local::lib's + stuff does. It can't. It's only a module import, so it B<only affects the + specific perl VM instance in which you load and import() it>. + + If you want to cascade it across invocations, you can set the PERL5OPT + environment variable to '-Mlib::core::only' and it'll sort of work. But be + aware that taint mode ignores this, so some modules' build and test code + probably will as well. + + You also need to be aware that perl's command line options are not processed + in order - -I options take effect before -M options, so + + perl -Mlib::core::only -Ilib + + is unlike to do what you want - it's exactly equivalent to: + + perl -Mlib::core::only + + If you want to combine a core-only @INC with additional paths, you need to + add the additional paths using -M options and the L<lib|lib> module: + + perl -Mlib::core::only -Mlib=lib + + # or if you're trying to test compiled code: + + perl -Mlib::core::only -Mblib + + For more information on the impossibility of sanely propagating this across + module builds without help from the build program, see + L<http://www.shadowcat.co.uk/blog/matt-s-trout/tainted-love> - and for ways + to achieve the old --self-contained feature's results, look at + L<App::FatPacker|App::FatPacker>'s tree function, and at + L<App::cpanminus|cpanm>'s --local-lib-contained feature. + + =head1 AUTHOR + + Matt S. Trout <mst@shadowcat.co.uk> + + =head1 LICENSE + + This library is free software under the same terms as perl itself. + + =head1 COPYRIGHT + + (c) 2010 the lib::core::only L</AUTHOR> as specified above. + + =cut + + 1; +LIB_CORE_ONLY + +$fatpacked{"local/lib.pm"} = <<'LOCAL_LIB'; + use strict; + use warnings; + + package local::lib; + + use 5.008001; # probably works with earlier versions but I'm not supporting them + # (patches would, of course, be welcome) + + use File::Spec (); + use File::Path (); + use Carp (); + use Config; + + our $VERSION = '1.008001'; # 1.8.1 + + our @KNOWN_FLAGS = qw(--self-contained); + + sub import { + my ($class, @args) = @_; + + # Remember what PERL5LIB was when we started + my $perl5lib = $ENV{PERL5LIB} || ''; + + my %arg_store; + for my $arg (@args) { + # check for lethal dash first to stop processing before causing problems + if ($arg =~ /−/) { + die <<'DEATH'; + WHOA THERE! It looks like you've got some fancy dashes in your commandline! + These are *not* the traditional -- dashes that software recognizes. You + probably got these by copy-pasting from the perldoc for this module as + rendered by a UTF8-capable formatter. This most typically happens on an OS X + terminal, but can happen elsewhere too. Please try again after replacing the + dashes with normal minus signs. + DEATH + } + elsif(grep { $arg eq $_ } @KNOWN_FLAGS) { + (my $flag = $arg) =~ s/--//; + $arg_store{$flag} = 1; + } + elsif($arg =~ /^--/) { + die "Unknown import argument: $arg"; + } + else { + # assume that what's left is a path + $arg_store{path} = $arg; + } + } + + if($arg_store{'self-contained'}) { + die "FATAL: The local::lib --self-contained flag has never worked reliably and the original author, Mark Stosberg, was unable or unwilling to maintain it. As such, this flag has been removed from the local::lib codebase in order to prevent misunderstandings and potentially broken builds. The local::lib authors recommend that you look at the lib::core::only module shipped with this distribution in order to create a more robust environment that is equivalent to what --self-contained provided (although quite possibly not what you originally thought it provided due to the poor quality of the documentation, for which we apologise).\n"; + } + + $arg_store{path} = $class->resolve_path($arg_store{path}); + $class->setup_local_lib_for($arg_store{path}); + + for (@INC) { # Untaint @INC + next if ref; # Skip entry if it is an ARRAY, CODE, blessed, etc. + m/(.*)/ and $_ = $1; + } + } + + sub pipeline; + + sub pipeline { + my @methods = @_; + my $last = pop(@methods); + if (@methods) { + \sub { + my ($obj, @args) = @_; + $obj->${pipeline @methods}( + $obj->$last(@args) + ); + }; + } else { + \sub { + shift->$last(@_); + }; + } + } + + =begin testing + + #:: test pipeline + + package local::lib; + + { package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } } + my $foo = bless({}, 'Foo'); + Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15); + + =end testing + + =cut + + sub _uniq { + my %seen; + grep { ! $seen{$_}++ } @_; + } + + sub resolve_path { + my ($class, $path) = @_; + $class->${pipeline qw( + resolve_relative_path + resolve_home_path + resolve_empty_path + )}($path); + } + + sub resolve_empty_path { + my ($class, $path) = @_; + if (defined $path) { + $path; + } else { + '~/perl5'; + } + } + + =begin testing + + #:: test classmethod setup + + my $c = 'local::lib'; + + =end testing + + =begin testing + + #:: test classmethod + + is($c->resolve_empty_path, '~/perl5'); + is($c->resolve_empty_path('foo'), 'foo'); + + =end testing + + =cut + + sub resolve_home_path { + my ($class, $path) = @_; + return $path unless ($path =~ /^~/); + my ($user) = ($path =~ /^~([^\/]+)/); # can assume ^~ so undef for 'us' + my $tried_file_homedir; + my $homedir = do { + if (eval { require File::HomeDir } && $File::HomeDir::VERSION >= 0.65) { + $tried_file_homedir = 1; + if (defined $user) { + File::HomeDir->users_home($user); + } else { + File::HomeDir->my_home; + } + } else { + if (defined $user) { + (getpwnam $user)[7]; + } else { + if (defined $ENV{HOME}) { + $ENV{HOME}; + } else { + (getpwuid $<)[7]; + } + } + } + }; + unless (defined $homedir) { + Carp::croak( + "Couldn't resolve homedir for " + .(defined $user ? $user : 'current user') + .($tried_file_homedir ? '' : ' - consider installing File::HomeDir') + ); + } + $path =~ s/^~[^\/]*/$homedir/; + $path; + } + + sub resolve_relative_path { + my ($class, $path) = @_; + $path = File::Spec->rel2abs($path); + } + + =begin testing + + #:: test classmethod + + local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; }; + is($c->resolve_relative_path('bar'),'FOObar'); + + =end testing + + =cut + + sub setup_local_lib_for { + my ($class, $path) = @_; + $path = $class->ensure_dir_structure_for($path); + if ($0 eq '-') { + $class->print_environment_vars_for($path); + exit 0; + } else { + $class->setup_env_hash_for($path); + @INC = _uniq(split($Config{path_sep}, $ENV{PERL5LIB}), @INC); + } + } + + sub install_base_bin_path { + my ($class, $path) = @_; + File::Spec->catdir($path, 'bin'); + } + + sub install_base_perl_path { + my ($class, $path) = @_; + File::Spec->catdir($path, 'lib', 'perl5'); + } + + sub install_base_arch_path { + my ($class, $path) = @_; + File::Spec->catdir($class->install_base_perl_path($path), $Config{archname}); + } + + sub ensure_dir_structure_for { + my ($class, $path) = @_; + unless (-d $path) { + warn "Attempting to create directory ${path}\n"; + } + File::Path::mkpath($path); + # Need to have the path exist to make a short name for it, so + # converting to a short name here. + $path = Win32::GetShortPathName($path) if $^O eq 'MSWin32'; + + return $path; + } + + sub INTERPOLATE_ENV () { 1 } + sub LITERAL_ENV () { 0 } + + sub guess_shelltype { + my $shellbin = 'sh'; + if(defined $ENV{'SHELL'}) { + my @shell_bin_path_parts = File::Spec->splitpath($ENV{'SHELL'}); + $shellbin = $shell_bin_path_parts[-1]; + } + my $shelltype = do { + local $_ = $shellbin; + if(/csh/) { + 'csh' + } else { + 'bourne' + } + }; + + # Both Win32 and Cygwin have $ENV{COMSPEC} set. + if (defined $ENV{'COMSPEC'} && $^O ne 'cygwin') { + my @shell_bin_path_parts = File::Spec->splitpath($ENV{'COMSPEC'}); + $shellbin = $shell_bin_path_parts[-1]; + $shelltype = do { + local $_ = $shellbin; + if(/command\.com/) { + 'win32' + } elsif(/cmd\.exe/) { + 'win32' + } elsif(/4nt\.exe/) { + 'win32' + } else { + $shelltype + } + }; + } + return $shelltype; + } + + sub print_environment_vars_for { + my ($class, $path) = @_; + print $class->environment_vars_string_for($path); + } + + sub environment_vars_string_for { + my ($class, $path) = @_; + my @envs = $class->build_environment_vars_for($path, LITERAL_ENV); + my $out = ''; + + # rather basic csh detection, goes on the assumption that something won't + # call itself csh unless it really is. also, default to bourne in the + # pathological situation where a user doesn't have $ENV{SHELL} defined. + # note also that shells with funny names, like zoid, are assumed to be + # bourne. + + my $shelltype = $class->guess_shelltype; + + while (@envs) { + my ($name, $value) = (shift(@envs), shift(@envs)); + $value =~ s/(\\")/\\$1/g; + $out .= $class->${\"build_${shelltype}_env_declaration"}($name, $value); + } + return $out; + } + + # simple routines that take two arguments: an %ENV key and a value. return + # strings that are suitable for passing directly to the relevant shell to set + # said key to said value. + sub build_bourne_env_declaration { + my $class = shift; + my($name, $value) = @_; + return qq{export ${name}="${value}"\n}; + } + + sub build_csh_env_declaration { + my $class = shift; + my($name, $value) = @_; + return qq{setenv ${name} "${value}"\n}; + } + + sub build_win32_env_declaration { + my $class = shift; + my($name, $value) = @_; + return qq{set ${name}=${value}\n}; + } + + sub setup_env_hash_for { + my ($class, $path) = @_; + my %envs = $class->build_environment_vars_for($path, INTERPOLATE_ENV); + @ENV{keys %envs} = values %envs; + } + + sub build_environment_vars_for { + my ($class, $path, $interpolate) = @_; + return ( + PERL_LOCAL_LIB_ROOT => $path, + PERL_MB_OPT => "--install_base ${path}", + PERL_MM_OPT => "INSTALL_BASE=${path}", + PERL5LIB => join($Config{path_sep}, + $class->install_base_arch_path($path), + $class->install_base_perl_path($path), + (($ENV{PERL5LIB}||()) ? + ($interpolate == INTERPOLATE_ENV + ? ($ENV{PERL5LIB}) + : (($^O ne 'MSWin32') ? '$PERL5LIB' : '%PERL5LIB%' )) + : ()) + ), + PATH => join($Config{path_sep}, + $class->install_base_bin_path($path), + ($interpolate == INTERPOLATE_ENV + ? ($ENV{PATH}||()) + : (($^O ne 'MSWin32') ? '$PATH' : '%PATH%' )) + ), + ) + } + + =begin testing + + #:: test classmethod + + File::Path::rmtree('t/var/splat'); + + $c->ensure_dir_structure_for('t/var/splat'); + + ok(-d 't/var/splat'); + + =end testing + + =encoding utf8 + + =head1 NAME + + local::lib - create and use a local lib/ for perl modules with PERL5LIB + + =head1 SYNOPSIS + + In code - + + use local::lib; # sets up a local lib at ~/perl5 + + use local::lib '~/foo'; # same, but ~/foo + + # Or... + use FindBin; + use local::lib "$FindBin::Bin/../support"; # app-local support library + + From the shell - + + # Install LWP and its missing dependencies to the '~/perl5' directory + perl -MCPAN -Mlocal::lib -e 'CPAN::install(LWP)' + + # Just print out useful shell commands + $ perl -Mlocal::lib + export PERL_MB_OPT='--install_base /home/username/perl5' + export PERL_MM_OPT='INSTALL_BASE=/home/username/perl5' + export PERL5LIB='/home/username/perl5/lib/perl5/i386-linux:/home/username/perl5/lib/perl5' + export PATH="/home/username/perl5/bin:$PATH" + + =head2 The bootstrapping technique + + A typical way to install local::lib is using what is known as the + "bootstrapping" technique. You would do this if your system administrator + hasn't already installed local::lib. In this case, you'll need to install + local::lib in your home directory. + + If you do have administrative privileges, you will still want to set up your + environment variables, as discussed in step 4. Without this, you would still + install the modules into the system CPAN installation and also your Perl scripts + will not use the lib/ path you bootstrapped with local::lib. + + By default local::lib installs itself and the CPAN modules into ~/perl5. + + Windows users must also see L</Differences when using this module under Win32>. + + 1. Download and unpack the local::lib tarball from CPAN (search for "Download" + on the CPAN page about local::lib). Do this as an ordinary user, not as root + or administrator. Unpack the file in your home directory or in any other + convenient location. + + 2. Run this: + + perl Makefile.PL --bootstrap + + If the system asks you whether it should automatically configure as much + as possible, you would typically answer yes. + + In order to install local::lib into a directory other than the default, you need + to specify the name of the directory when you call bootstrap, as follows: + + perl Makefile.PL --bootstrap=~/foo + + 3. Run this: (local::lib assumes you have make installed on your system) + + make test && make install + + 4. Now we need to setup the appropriate environment variables, so that Perl + starts using our newly generated lib/ directory. If you are using bash or + any other Bourne shells, you can add this to your shell startup script this + way: + + echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc + + If you are using C shell, you can do this as follows: + + /bin/csh + echo $SHELL + /bin/csh + perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc + + If you passed to bootstrap a directory other than default, you also need to give that as + import parameter to the call of the local::lib module like this way: + + echo 'eval $(perl -I$HOME/foo/lib/perl5 -Mlocal::lib=$HOME/foo)' >>~/.bashrc + + After writing your shell configuration file, be sure to re-read it to get the + changed settings into your current shell's environment. Bourne shells use + C<. ~/.bashrc> for this, whereas C shells use C<source ~/.cshrc>. + + If you're on a slower machine, or are operating under draconian disk space + limitations, you can disable the automatic generation of manpages from POD when + installing modules by using the C<--no-manpages> argument when bootstrapping: + + perl Makefile.PL --bootstrap --no-manpages + + To avoid doing several bootstrap for several Perl module environments on the + same account, for example if you use it for several different deployed + applications independently, you can use one bootstrapped local::lib + installation to install modules in different directories directly this way: + + cd ~/mydir1 + perl -Mlocal::lib=./ + eval $(perl -Mlocal::lib=./) ### To set the environment for this shell alone + printenv ### You will see that ~/mydir1 is in the PERL5LIB + perl -MCPAN -e install ... ### whatever modules you want + cd ../mydir2 + ... REPEAT ... + + For multiple environments for multiple apps you may need to include a modified + version of the C<< use FindBin >> instructions in the "In code" sample above. + If you did something like the above, you have a set of Perl modules at C<< + ~/mydir1/lib >>. If you have a script at C<< ~/mydir1/scripts/myscript.pl >>, + you need to tell it where to find the modules you installed for it at C<< + ~/mydir1/lib >>. + + In C<< ~/mydir1/scripts/myscript.pl >>: + + use strict; + use warnings; + use local::lib "$FindBin::Bin/.."; ### points to ~/mydir1 and local::lib finds lib + use lib "$FindBin::Bin/../lib"; ### points to ~/mydir1/lib + + Put this before any BEGIN { ... } blocks that require the modules you installed. + + =head2 Differences when using this module under Win32 + + To set up the proper environment variables for your current session of + C<CMD.exe>, you can use this: + + C:\>perl -Mlocal::lib + set PERL_MB_OPT=--install_base C:\DOCUME~1\ADMINI~1\perl5 + set PERL_MM_OPT=INSTALL_BASE=C:\DOCUME~1\ADMINI~1\perl5 + set PERL5LIB=C:\DOCUME~1\ADMINI~1\perl5\lib\perl5;C:\DOCUME~1\ADMINI~1\perl5\lib\perl5\MSWin32-x86-multi-thread + set PATH=C:\DOCUME~1\ADMINI~1\perl5\bin;%PATH% + + ### To set the environment for this shell alone + C:\>perl -Mlocal::lib > %TEMP%\tmp.bat && %TEMP%\tmp.bat && del %TEMP%\temp.bat + ### instead of $(perl -Mlocal::lib=./) + + If you want the environment entries to persist, you'll need to add then to the + Control Panel's System applet yourself or use L<App::local::lib::Win32Helper>. + + The "~" is translated to the user's profile directory (the directory named for + the user under "Documents and Settings" (Windows XP or earlier) or "Users" + (Windows Vista or later)) unless $ENV{HOME} exists. After that, the home + directory is translated to a short name (which means the directory must exist) + and the subdirectories are created. + + =head1 RATIONALE + + The version of a Perl package on your machine is not always the version you + need. Obviously, the best thing to do would be to update to the version you + need. However, you might be in a situation where you're prevented from doing + this. Perhaps you don't have system administrator privileges; or perhaps you + are using a package management system such as Debian, and nobody has yet gotten + around to packaging up the version you need. + + local::lib solves this problem by allowing you to create your own directory of + Perl packages downloaded from CPAN (in a multi-user system, this would typically + be within your own home directory). The existing system Perl installation is + not affected; you simply invoke Perl with special options so that Perl uses the + packages in your own local package directory rather than the system packages. + local::lib arranges things so that your locally installed version of the Perl + packages takes precedence over the system installation. + + If you are using a package management system (such as Debian), you don't need to + worry about Debian and CPAN stepping on each other's toes. Your local version + of the packages will be written to an entirely separate directory from those + installed by Debian. + + =head1 DESCRIPTION + + This module provides a quick, convenient way of bootstrapping a user-local Perl + module library located within the user's home directory. It also constructs and + prints out for the user the list of environment variables using the syntax + appropriate for the user's current shell (as specified by the C<SHELL> + environment variable), suitable for directly adding to one's shell + configuration file. + + More generally, local::lib allows for the bootstrapping and usage of a + directory containing Perl modules outside of Perl's C<@INC>. This makes it + easier to ship an application with an app-specific copy of a Perl module, or + collection of modules. Useful in cases like when an upstream maintainer hasn't + applied a patch to a module of theirs that you need for your application. + + On import, local::lib sets the following environment variables to appropriate + values: + + =over 4 + + =item PERL_MB_OPT + + =item PERL_MM_OPT + + =item PERL5LIB + + =item PATH + + PATH is appended to, rather than clobbered. + + =back + + These values are then available for reference by any code after import. + + =head1 CREATING A SELF-CONTAINED SET OF MODULES + + See L<lib::core::only> for one way to do this - but note that + there are a number of caveats, and the best approach is always to perform a + build against a clean perl (i.e. site and vendor as close to empty as possible). + + =head1 METHODS + + =head2 ensure_dir_structure_for + + =over 4 + + =item Arguments: $path + + =item Return value: None + + =back + + Attempts to create the given path, and all required parent directories. Throws + an exception on failure. + + =head2 print_environment_vars_for + + =over 4 + + =item Arguments: $path + + =item Return value: None + + =back + + Prints to standard output the variables listed above, properly set to use the + given path as the base directory. + + =head2 build_environment_vars_for + + =over 4 + + =item Arguments: $path, $interpolate + + =item Return value: \%environment_vars + + =back + + Returns a hash with the variables listed above, properly set to use the + given path as the base directory. + + =head2 setup_env_hash_for + + =over 4 + + =item Arguments: $path + + =item Return value: None + + =back + + Constructs the C<%ENV> keys for the given path, by calling + L</build_environment_vars_for>. + + =head2 install_base_perl_path + + =over 4 + + =item Arguments: $path + + =item Return value: $install_base_perl_path + + =back + + Returns a path describing where to install the Perl modules for this local + library installation. Appends the directories C<lib> and C<perl5> to the given + path. + + =head2 install_base_arch_path + + =over 4 + + =item Arguments: $path + + =item Return value: $install_base_arch_path + + =back + + Returns a path describing where to install the architecture-specific Perl + modules for this local library installation. Based on the + L</install_base_perl_path> method's return value, and appends the value of + C<$Config{archname}>. + + =head2 install_base_bin_path + + =over 4 + + =item Arguments: $path + + =item Return value: $install_base_bin_path + + =back + + Returns a path describing where to install the executable programs for this + local library installation. Based on the L</install_base_perl_path> method's + return value, and appends the directory C<bin>. + + =head2 resolve_empty_path + + =over 4 + + =item Arguments: $path + + =item Return value: $base_path + + =back + + Builds and returns the base path into which to set up the local module + installation. Defaults to C<~/perl5>. + + =head2 resolve_home_path + + =over 4 + + =item Arguments: $path + + =item Return value: $home_path + + =back + + Attempts to find the user's home directory. If installed, uses C<File::HomeDir> + for this purpose. If no definite answer is available, throws an exception. + + =head2 resolve_relative_path + + =over 4 + + =item Arguments: $path + + =item Return value: $absolute_path + + =back + + Translates the given path into an absolute path. + + =head2 resolve_path + + =over 4 + + =item Arguments: $path + + =item Return value: $absolute_path + + =back + + Calls the following in a pipeline, passing the result from the previous to the + next, in an attempt to find where to configure the environment for a local + library installation: L</resolve_empty_path>, L</resolve_home_path>, + L</resolve_relative_path>. Passes the given path argument to + L</resolve_empty_path> which then returns a result that is passed to + L</resolve_home_path>, which then has its result passed to + L</resolve_relative_path>. The result of this final call is returned from + L</resolve_path>. + + =head1 A WARNING ABOUT UNINST=1 + + Be careful about using local::lib in combination with "make install UNINST=1". + The idea of this feature is that will uninstall an old version of a module + before installing a new one. However it lacks a safety check that the old + version and the new version will go in the same directory. Used in combination + with local::lib, you can potentially delete a globally accessible version of a + module while installing the new version in a local place. Only combine "make + install UNINST=1" and local::lib if you understand these possible consequences. + + =head1 LIMITATIONS + + The perl toolchain is unable to handle directory names with spaces in it, + so you cant put your local::lib bootstrap into a directory with spaces. What + you can do is moving your local::lib to a directory with spaces B<after> you + installed all modules inside your local::lib bootstrap. But be aware that you + cant update or install CPAN modules after the move. + + Rather basic shell detection. Right now anything with csh in its name is + assumed to be a C shell or something compatible, and everything else is assumed + to be Bourne, except on Win32 systems. If the C<SHELL> environment variable is + not set, a Bourne-compatible shell is assumed. + + Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you + have CPANPLUS installed. + + Kills any existing PERL5LIB, PERL_MM_OPT or PERL_MB_OPT. + + Should probably auto-fixup CPAN config if not already done. + + Patches very much welcome for any of the above. + + On Win32 systems, does not have a way to write the created environment variables + to the registry, so that they can persist through a reboot. + + =head1 TROUBLESHOOTING + + If you've configured local::lib to install CPAN modules somewhere in to your + home directory, and at some point later you try to install a module with C<cpan + -i Foo::Bar>, but it fails with an error like: C<Warning: You do not have + permissions to install into /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux at + /usr/lib64/perl5/5.8.8/Foo/Bar.pm> and buried within the install log is an + error saying C<'INSTALL_BASE' is not a known MakeMaker parameter name>, then + you've somehow lost your updated ExtUtils::MakeMaker module. + + To remedy this situation, rerun the bootstrapping procedure documented above. + + Then, run C<rm -r ~/.cpan/build/Foo-Bar*> + + Finally, re-run C<cpan -i Foo::Bar> and it should install without problems. + + =head1 ENVIRONMENT + + =over 4 + + =item SHELL + + =item COMSPEC + + local::lib looks at the user's C<SHELL> environment variable when printing out + commands to add to the shell configuration file. + + On Win32 systems, C<COMSPEC> is also examined. + + =back + + =head1 SUPPORT + + IRC: + + Join #local-lib on irc.perl.org. + + =head1 AUTHOR + + Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/ + + auto_install fixes kindly sponsored by http://www.takkle.com/ + + =head1 CONTRIBUTORS + + Patches to correctly output commands for csh style shells, as well as some + documentation additions, contributed by Christopher Nehren <apeiron@cpan.org>. + + Doc patches for a custom local::lib directory, more cleanups in the english + documentation and a L<german documentation|POD2::DE::local::lib> contributed by Torsten Raudssus + <torsten@raudssus.de>. + + Hans Dieter Pearcey <hdp@cpan.org> sent in some additional tests for ensuring + things will install properly, submitted a fix for the bug causing problems with + writing Makefiles during bootstrapping, contributed an example program, and + submitted yet another fix to ensure that local::lib can install and bootstrap + properly. Many, many thanks! + + pattern of Freenode IRC contributed the beginnings of the Troubleshooting + section. Many thanks! + + Patch to add Win32 support contributed by Curtis Jewell <csjewell@cpan.org>. + + Warnings for missing PATH/PERL5LIB (as when not running interactively) silenced + by a patch from Marco Emilio Poleggi. + + Mark Stosberg <mark@summersault.com> provided the code for the now deleted + '--self-contained' option. + + Documentation patches to make win32 usage clearer by + David Mertens <dcmertens.perl@gmail.com> (run4flat). + + Brazilian L<portuguese translation|POD2::PT_BR::local::lib> and minor doc patches contributed by Breno + G. de Oliveira <garu@cpan.org>. + + =head1 COPYRIGHT + + Copyright (c) 2007 - 2010 the local::lib L</AUTHOR> and L</CONTRIBUTORS> as + listed above. + + =head1 LICENSE + + This library is free software and may be distributed under the same terms + as perl itself. + + =cut + + 1; +LOCAL_LIB + +$fatpacked{"version.pm"} = <<'VERSION'; + #!perl -w + package version; + + use 5.005_04; + use strict; + + use vars qw(@ISA $VERSION $CLASS $STRICT $LAX *declare *qv); + + $VERSION = 0.88; + + $CLASS = 'version'; + + #--------------------------------------------------------------------------# + # Version regexp components + #--------------------------------------------------------------------------# + + # Fraction part of a decimal version number. This is a common part of + # both strict and lax decimal versions + + my $FRACTION_PART = qr/\.[0-9]+/; + + # First part of either decimal or dotted-decimal strict version number. + # Unsigned integer with no leading zeroes (except for zero itself) to + # avoid confusion with octal. + + my $STRICT_INTEGER_PART = qr/0|[1-9][0-9]*/; + + # First part of either decimal or dotted-decimal lax version number. + # Unsigned integer, but allowing leading zeros. Always interpreted + # as decimal. However, some forms of the resulting syntax give odd + # results if used as ordinary Perl expressions, due to how perl treats + # octals. E.g. + # version->new("010" ) == 10 + # version->new( 010 ) == 8 + # version->new( 010.2) == 82 # "8" . "2" + + my $LAX_INTEGER_PART = qr/[0-9]+/; + + # Second and subsequent part of a strict dotted-decimal version number. + # Leading zeroes are permitted, and the number is always decimal. + # Limited to three digits to avoid overflow when converting to decimal + # form and also avoid problematic style with excessive leading zeroes. + + my $STRICT_DOTTED_DECIMAL_PART = qr/\.[0-9]{1,3}/; + + # Second and subsequent part of a lax dotted-decimal version number. + # Leading zeroes are permitted, and the number is always decimal. No + # limit on the numerical value or number of digits, so there is the + # possibility of overflow when converting to decimal form. + + my $LAX_DOTTED_DECIMAL_PART = qr/\.[0-9]+/; + + # Alpha suffix part of lax version number syntax. Acts like a + # dotted-decimal part. + + my $LAX_ALPHA_PART = qr/_[0-9]+/; + + #--------------------------------------------------------------------------# + # Strict version regexp definitions + #--------------------------------------------------------------------------# + + # Strict decimal version number. + + my $STRICT_DECIMAL_VERSION = + qr/ $STRICT_INTEGER_PART $FRACTION_PART? /x; + + # Strict dotted-decimal version number. Must have both leading "v" and + # at least three parts, to avoid confusion with decimal syntax. + + my $STRICT_DOTTED_DECIMAL_VERSION = + qr/ v $STRICT_INTEGER_PART $STRICT_DOTTED_DECIMAL_PART{2,} /x; + + # Complete strict version number syntax -- should generally be used + # anchored: qr/ \A $STRICT \z /x + + $STRICT = + qr/ $STRICT_DECIMAL_VERSION | $STRICT_DOTTED_DECIMAL_VERSION /x; + + #--------------------------------------------------------------------------# + # Lax version regexp definitions + #--------------------------------------------------------------------------# + + # Lax decimal version number. Just like the strict one except for + # allowing an alpha suffix or allowing a leading or trailing + # decimal-point + + my $LAX_DECIMAL_VERSION = + qr/ $LAX_INTEGER_PART (?: \. | $FRACTION_PART $LAX_ALPHA_PART? )? + | + $FRACTION_PART $LAX_ALPHA_PART? + /x; + + # Lax dotted-decimal version number. Distinguished by having either + # leading "v" or at least three non-alpha parts. Alpha part is only + # permitted if there are at least two non-alpha parts. Strangely + # enough, without the leading "v", Perl takes .1.2 to mean v0.1.2, + # so when there is no "v", the leading part is optional + + my $LAX_DOTTED_DECIMAL_VERSION = + qr/ + v $LAX_INTEGER_PART (?: $LAX_DOTTED_DECIMAL_PART+ $LAX_ALPHA_PART? )? + | + $LAX_INTEGER_PART? $LAX_DOTTED_DECIMAL_PART{2,} $LAX_ALPHA_PART? + /x; + + # Complete lax version number syntax -- should generally be used + # anchored: qr/ \A $LAX \z /x + # + # The string 'undef' is a special case to make for easier handling + # of return values from ExtUtils::MM->parse_version + + $LAX = + qr/ undef | $LAX_DECIMAL_VERSION | $LAX_DOTTED_DECIMAL_VERSION /x; + + #--------------------------------------------------------------------------# + + eval "use version::vxs $VERSION"; + if ( $@ ) { # don't have the XS version installed + eval "use version::vpp $VERSION"; # don't tempt fate + die "$@" if ( $@ ); + push @ISA, "version::vpp"; + local $^W; + *version::qv = \&version::vpp::qv; + *version::declare = \&version::vpp::declare; + *version::_VERSION = \&version::vpp::_VERSION; + if ($] >= 5.009000 && $] < 5.011004) { + no strict 'refs'; + *version::stringify = \&version::vpp::stringify; + *{'version::(""'} = \&version::vpp::stringify; + *version::new = \&version::vpp::new; + *version::parse = \&version::vpp::parse; + } + } + else { # use XS module + push @ISA, "version::vxs"; + local $^W; + *version::declare = \&version::vxs::declare; + *version::qv = \&version::vxs::qv; + *version::_VERSION = \&version::vxs::_VERSION; + *version::vcmp = \&version::vxs::VCMP; + if ($] >= 5.009000 && $] < 5.011004) { + no strict 'refs'; + *version::stringify = \&version::vxs::stringify; + *{'version::(""'} = \&version::vxs::stringify; + *version::new = \&version::vxs::new; + *version::parse = \&version::vxs::parse; + } + + } + + # Preloaded methods go here. + sub import { + no strict 'refs'; + my ($class) = shift; + + # Set up any derived class + unless ($class eq 'version') { + local $^W; + *{$class.'::declare'} = \&version::declare; + *{$class.'::qv'} = \&version::qv; + } + + my %args; + if (@_) { # any remaining terms are arguments + map { $args{$_} = 1 } @_ + } + else { # no parameters at all on use line + %args = + ( + qv => 1, + 'UNIVERSAL::VERSION' => 1, + ); + } + + my $callpkg = caller(); + + if (exists($args{declare})) { + *{$callpkg.'::declare'} = + sub {return $class->declare(shift) } + unless defined(&{$callpkg.'::declare'}); + } + + if (exists($args{qv})) { + *{$callpkg.'::qv'} = + sub {return $class->qv(shift) } + unless defined(&{$callpkg.'::qv'}); + } + + if (exists($args{'UNIVERSAL::VERSION'})) { + local $^W; + *UNIVERSAL::VERSION + = \&version::_VERSION; + } + + if (exists($args{'VERSION'})) { + *{$callpkg.'::VERSION'} = \&version::_VERSION; + } + + if (exists($args{'is_strict'})) { + *{$callpkg.'::is_strict'} = \&version::is_strict + unless defined(&{$callpkg.'::is_strict'}); + } + + if (exists($args{'is_lax'})) { + *{$callpkg.'::is_lax'} = \&version::is_lax + unless defined(&{$callpkg.'::is_lax'}); + } + } + + sub is_strict { defined $_[0] && $_[0] =~ qr/ \A $STRICT \z /x } + sub is_lax { defined $_[0] && $_[0] =~ qr/ \A $LAX \z /x } + + 1; +VERSION + +$fatpacked{"version/vpp.pm"} = <<'VERSION_VPP'; + package charstar; + # a little helper class to emulate C char* semantics in Perl + # so that prescan_version can use the same code as in C + + use overload ( + '""' => \&thischar, + '0+' => \&thischar, + '++' => \&increment, + '--' => \&decrement, + '+' => \&plus, + '-' => \&minus, + '*' => \&multiply, + 'cmp' => \&cmp, + '<=>' => \&spaceship, + 'bool' => \&thischar, + '=' => \&clone, + ); + + sub new { + my ($self, $string) = @_; + my $class = ref($self) || $self; + + my $obj = { + string => [split(//,$string)], + current => 0, + }; + return bless $obj, $class; + } + + sub thischar { + my ($self) = @_; + my $last = $#{$self->{string}}; + my $curr = $self->{current}; + if ($curr >= 0 && $curr <= $last) { + return $self->{string}->[$curr]; + } + else { + return ''; + } + } + + sub increment { + my ($self) = @_; + $self->{current}++; + } + + sub decrement { + my ($self) = @_; + $self->{current}--; + } + + sub plus { + my ($self, $offset) = @_; + my $rself = $self->clone; + $rself->{current} += $offset; + return $rself; + } + + sub minus { + my ($self, $offset) = @_; + my $rself = $self->clone; + $rself->{current} -= $offset; + return $rself; + } + + sub multiply { + my ($left, $right, $swapped) = @_; + my $char = $left->thischar(); + return $char * $right; + } + + sub spaceship { + my ($left, $right, $swapped) = @_; + unless (ref($right)) { # not an object already + $right = $left->new($right); + } + return $left->{current} <=> $right->{current}; + } + + sub cmp { + my ($left, $right, $swapped) = @_; + unless (ref($right)) { # not an object already + if (length($right) == 1) { # comparing single character only + return $left->thischar cmp $right; + } + $right = $left->new($right); + } + return $left->currstr cmp $right->currstr; + } + + sub bool { + my ($self) = @_; + my $char = $self->thischar; + return ($char ne ''); + } + + sub clone { + my ($left, $right, $swapped) = @_; + $right = { + string => [@{$left->{string}}], + current => $left->{current}, + }; + return bless $right, ref($left); + } + + sub currstr { + my ($self, $s) = @_; + my $curr = $self->{current}; + my $last = $#{$self->{string}}; + if (defined($s) && $s->{current} < $last) { + $last = $s->{current}; + } + + my $string = join('', @{$self->{string}}[$curr..$last]); + return $string; + } + + package version::vpp; + use strict; + + use POSIX qw/locale_h/; + use locale; + use vars qw ($VERSION @ISA @REGEXS); + $VERSION = 0.88; + + use overload ( + '""' => \&stringify, + '0+' => \&numify, + 'cmp' => \&vcmp, + '<=>' => \&vcmp, + 'bool' => \&vbool, + 'nomethod' => \&vnoop, + ); + + eval "use warnings"; + if ($@) { + eval ' + package warnings; + sub enabled {return $^W;} + 1; + '; + } + + my $VERSION_MAX = 0x7FFFFFFF; + + # implement prescan_version as closely to the C version as possible + use constant TRUE => 1; + use constant FALSE => 0; + + sub isDIGIT { + my ($char) = shift->thischar(); + return ($char =~ /\d/); + } + + sub isALPHA { + my ($char) = shift->thischar(); + return ($char =~ /[a-zA-Z]/); + } + + sub isSPACE { + my ($char) = shift->thischar(); + return ($char =~ /\s/); + } + + sub BADVERSION { + my ($s, $errstr, $error) = @_; + if ($errstr) { + $$errstr = $error; + } + return $s; + } + + sub prescan_version { + my ($s, $strict, $errstr, $sqv, $ssaw_decimal, $swidth, $salpha) = @_; + my $qv = defined $sqv ? $$sqv : FALSE; + my $saw_decimal = defined $ssaw_decimal ? $$ssaw_decimal : 0; + my $width = defined $swidth ? $$swidth : 3; + my $alpha = defined $salpha ? $$salpha : FALSE; + + my $d = $s; + + if ($qv && isDIGIT($d)) { + goto dotted_decimal_version; + } + + if ($d eq 'v') { # explicit v-string + $d++; + if (isDIGIT($d)) { + $qv = TRUE; + } + else { # degenerate v-string + # requires v1.2.3 + return BADVERSION($s,$errstr,"Invalid version format (dotted-decimal versions require at least three parts)"); + } + + dotted_decimal_version: + if ($strict && $d eq '0' && isDIGIT($d+1)) { + # no leading zeros allowed + return BADVERSION($s,$errstr,"Invalid version format (no leading zeros)"); + } + + while (isDIGIT($d)) { # integer part + $d++; + } + + if ($d eq '.') + { + $saw_decimal++; + $d++; # decimal point + } + else + { + if ($strict) { + # require v1.2.3 + return BADVERSION($s,$errstr,"Invalid version format (dotted-decimal versions require at least three parts)"); + } + else { + goto version_prescan_finish; + } + } + + { + my $i = 0; + my $j = 0; + while (isDIGIT($d)) { # just keep reading + $i++; + while (isDIGIT($d)) { + $d++; $j++; + # maximum 3 digits between decimal + if ($strict && $j > 3) { + return BADVERSION($s,$errstr,"Invalid version format (maximum 3 digits between decimals)"); + } + } + if ($d eq '_') { + if ($strict) { + return BADVERSION($s,$errstr,"Invalid version format (no underscores)"); + } + if ( $alpha ) { + return BADVERSION($s,$errstr,"Invalid version format (multiple underscores)"); + } + $d++; + $alpha = TRUE; + } + elsif ($d eq '.') { + if ($alpha) { + return BADVERSION($s,$errstr,"Invalid version format (underscores before decimal)"); + } + $saw_decimal++; + $d++; + } + elsif (!isDIGIT($d)) { + last; + } + $j = 0; + } + + if ($strict && $i < 2) { + # requires v1.2.3 + return BADVERSION($s,$errstr,"Invalid version format (dotted-decimal versions require at least three parts)"); + } + } + } # end if dotted-decimal + else + { # decimal versions + # special $strict case for leading '.' or '0' + if ($strict) { + if ($d eq '.') { + return BADVERSION($s,$errstr,"Invalid version format (0 before decimal required)"); + } + if ($d eq '0' && isDIGIT($d+1)) { + return BADVERSION($s,$errstr,"Invalid version format (no leading zeros)"); + } + } + + # consume all of the integer part + while (isDIGIT($d)) { + $d++; + } + + # look for a fractional part + if ($d eq '.') { + # we found it, so consume it + $saw_decimal++; + $d++; + } + elsif (!$d || $d eq ';' || isSPACE($d) || $d eq '}') { + if ( $d == $s ) { + # found nothing + return BADVERSION($s,$errstr,"Invalid version format (version required)"); + } + # found just an integer + goto version_prescan_finish; + } + elsif ( $d == $s ) { + # didn't find either integer or period + return BADVERSION($s,$errstr,"Invalid version format (non-numeric data)"); + } + elsif ($d eq '_') { + # underscore can't come after integer part + if ($strict) { + return BADVERSION($s,$errstr,"Invalid version format (no underscores)"); + } + elsif (isDIGIT($d+1)) { + return BADVERSION($s,$errstr,"Invalid version format (alpha without decimal)"); + } + else { + return BADVERSION($s,$errstr,"Invalid version format (misplaced underscore)"); + } + } + elsif ($d) { + # anything else after integer part is just invalid data + return BADVERSION($s,$errstr,"Invalid version format (non-numeric data)"); + } + + # scan the fractional part after the decimal point + if ($d && !isDIGIT($d) && ($strict || ! ($d eq ';' || isSPACE($d) || $d eq '}') )) { + # $strict or lax-but-not-the-end + return BADVERSION($s,$errstr,"Invalid version format (fractional part required)"); + } + + while (isDIGIT($d)) { + $d++; + if ($d eq '.' && isDIGIT($d-1)) { + if ($alpha) { + return BADVERSION($s,$errstr,"Invalid version format (underscores before decimal)"); + } + if ($strict) { + return BADVERSION($s,$errstr,"Invalid version format (dotted-decimal versions must begin with 'v')"); + } + $d = $s; # start all over again + $qv = TRUE; + goto dotted_decimal_version; + } + if ($d eq '_') { + if ($strict) { + return BADVERSION($s,$errstr,"Invalid version format (no underscores)"); + } + if ( $alpha ) { + return BADVERSION($s,$errstr,"Invalid version format (multiple underscores)"); + } + if ( ! isDIGIT($d+1) ) { + return BADVERSION($s,$errstr,"Invalid version format (misplaced underscore)"); + } + $d++; + $alpha = TRUE; + } + } + } + + version_prescan_finish: + while (isSPACE($d)) { + $d++; + } + + if ($d && !isDIGIT($d) && (! ($d eq ';' || $d eq '}') )) { + # trailing non-numeric data + return BADVERSION($s,$errstr,"Invalid version format (non-numeric data)"); + } + + if (defined $sqv) { + $$sqv = $qv; + } + if (defined $swidth) { + $$swidth = $width; + } + if (defined $ssaw_decimal) { + $$ssaw_decimal = $saw_decimal; + } + if (defined $salpha) { + $$salpha = $alpha; + } + return $d; + } + + sub scan_version { + my ($s, $rv, $qv) = @_; + my $start; + my $pos; + my $last; + my $errstr; + my $saw_decimal = 0; + my $width = 3; + my $alpha = FALSE; + my $vinf = FALSE; + my @av; + + $s = new charstar $s; + + while (isSPACE($s)) { # leading whitespace is OK + $s++; + } + + $last = prescan_version($s, FALSE, \$errstr, \$qv, \$saw_decimal, + \$width, \$alpha); + + if ($errstr) { + # 'undef' is a special case and not an error + if ( $s ne 'undef') { + use Carp; + Carp::croak($errstr); + } + } + + $start = $s; + if ($s eq 'v') { + $s++; + } + $pos = $s; + + if ( $qv ) { + $$rv->{qv} = $qv; + } + if ( $alpha ) { + $$rv->{alpha} = $alpha; + } + if ( !$qv && $width < 3 ) { + $$rv->{width} = $width; + } + + while (isDIGIT($pos)) { + $pos++; + } + if (!isALPHA($pos)) { + my $rev; + + for (;;) { + $rev = 0; + { + # this is atoi() that delimits on underscores + my $end = $pos; + my $mult = 1; + my $orev; + + # the following if() will only be true after the decimal + # point of a version originally created with a bare + # floating point number, i.e. not quoted in any way + # + if ( !$qv && $s > $start && $saw_decimal == 1 ) { + $mult *= 100; + while ( $s < $end ) { + $orev = $rev; + $rev += $s * $mult; + $mult /= 10; + if ( (abs($orev) > abs($rev)) + || (abs($rev) > $VERSION_MAX )) { + warn("Integer overflow in version %d", + $VERSION_MAX); + $s = $end - 1; + $rev = $VERSION_MAX; + $vinf = 1; + } + $s++; + if ( $s eq '_' ) { + $s++; + } + } + } + else { + while (--$end >= $s) { + $orev = $rev; + $rev += $end * $mult; + $mult *= 10; + if ( (abs($orev) > abs($rev)) + || (abs($rev) > $VERSION_MAX )) { + warn("Integer overflow in version"); + $end = $s - 1; + $rev = $VERSION_MAX; + $vinf = 1; + } + } + } + } + + # Append revision + push @av, $rev; + if ( $vinf ) { + $s = $last; + last; + } + elsif ( $pos eq '.' ) { + $s = ++$pos; + } + elsif ( $pos eq '_' && isDIGIT($pos+1) ) { + $s = ++$pos; + } + elsif ( $pos eq ',' && isDIGIT($pos+1) ) { + $s = ++$pos; + } + elsif ( isDIGIT($pos) ) { + $s = $pos; + } + else { + $s = $pos; + last; + } + if ( $qv ) { + while ( isDIGIT($pos) ) { + $pos++; + } + } + else { + my $digits = 0; + while ( ( isDIGIT($pos) || $pos eq '_' ) && $digits < 3 ) { + if ( $pos ne '_' ) { + $digits++; + } + $pos++; + } + } + } + } + if ( $qv ) { # quoted versions always get at least three terms + my $len = $#av; + # This for loop appears to trigger a compiler bug on OS X, as it + # loops infinitely. Yes, len is negative. No, it makes no sense. + # Compiler in question is: + # gcc version 3.3 20030304 (Apple Computer, Inc. build 1640) + # for ( len = 2 - len; len > 0; len-- ) + # av_push(MUTABLE_AV(sv), newSViv(0)); + # + $len = 2 - $len; + while ($len-- > 0) { + push @av, 0; + } + } + + # need to save off the current version string for later + if ( $vinf ) { + $$rv->{original} = "v.Inf"; + $$rv->{vinf} = 1; + } + elsif ( $s > $start ) { + $$rv->{original} = $start->currstr($s); + if ( $qv && $saw_decimal == 1 && $start ne 'v' ) { + # need to insert a v to be consistent + $$rv->{original} = 'v' . $$rv->{original}; + } + } + else { + $$rv->{original} = '0'; + push(@av, 0); + } + + # And finally, store the AV in the hash + $$rv->{version} = \@av; + + # fix RT#19517 - special case 'undef' as string + if ($s eq 'undef') { + $s += 5; + } + + return $s; + } + + sub new + { + my ($class, $value) = @_; + my $self = bless ({}, ref ($class) || $class); + my $qv = FALSE; + + if ( ref($value) && eval('$value->isa("version")') ) { + # Can copy the elements directly + $self->{version} = [ @{$value->{version} } ]; + $self->{qv} = 1 if $value->{qv}; + $self->{alpha} = 1 if $value->{alpha}; + $self->{original} = ''.$value->{original}; + return $self; + } + + my $currlocale = setlocale(LC_ALL); + + # if the current locale uses commas for decimal points, we + # just replace commas with decimal places, rather than changing + # locales + if ( localeconv()->{decimal_point} eq ',' ) { + $value =~ tr/,/./; + } + + if ( not defined $value or $value =~ /^undef$/ ) { + # RT #19517 - special case for undef comparison + # or someone forgot to pass a value + push @{$self->{version}}, 0; + $self->{original} = "0"; + return ($self); + } + + if ( $#_ == 2 ) { # must be CVS-style + $value = $_[2]; + $qv = TRUE; + } + + $value = _un_vstring($value); + + # exponential notation + if ( $value =~ /\d+.?\d*e[-+]?\d+/ ) { + $value = sprintf("%.9f",$value); + $value =~ s/(0+)$//; # trim trailing zeros + } + + my $s = scan_version($value, \$self, $qv); + + if ($s) { # must be something left over + warn("Version string '%s' contains invalid data; " + ."ignoring: '%s'", $value, $s); + } + + return ($self); + } + + *parse = \&new; + + sub numify + { + my ($self) = @_; + unless (_verify($self)) { + require Carp; + Carp::croak("Invalid version object"); + } + my $width = $self->{width} || 3; + my $alpha = $self->{alpha} || ""; + my $len = $#{$self->{version}}; + my $digit = $self->{version}[0]; + my $string = sprintf("%d.", $digit ); + + for ( my $i = 1 ; $i < $len ; $i++ ) { + $digit = $self->{version}[$i]; + if ( $width < 3 ) { + my $denom = 10**(3-$width); + my $quot = int($digit/$denom); + my $rem = $digit - ($quot * $denom); + $string .= sprintf("%0".$width."d_%d", $quot, $rem); + } + else { + $string .= sprintf("%03d", $digit); + } + } + + if ( $len > 0 ) { + $digit = $self->{version}[$len]; + if ( $alpha && $width == 3 ) { + $string .= "_"; + } + $string .= sprintf("%0".$width."d", $digit); + } + else # $len = 0 + { + $string .= sprintf("000"); + } + + return $string; + } + + sub normal + { + my ($self) = @_; + unless (_verify($self)) { + require Carp; + Carp::croak("Invalid version object"); + } + my $alpha = $self->{alpha} || ""; + my $len = $#{$self->{version}}; + my $digit = $self->{version}[0]; + my $string = sprintf("v%d", $digit ); + + for ( my $i = 1 ; $i < $len ; $i++ ) { + $digit = $self->{version}[$i]; + $string .= sprintf(".%d", $digit); + } + + if ( $len > 0 ) { + $digit = $self->{version}[$len]; + if ( $alpha ) { + $string .= sprintf("_%0d", $digit); + } + else { + $string .= sprintf(".%0d", $digit); + } + } + + if ( $len <= 2 ) { + for ( $len = 2 - $len; $len != 0; $len-- ) { + $string .= sprintf(".%0d", 0); + } + } + + return $string; + } + + sub stringify + { + my ($self) = @_; + unless (_verify($self)) { + require Carp; + Carp::croak("Invalid version object"); + } + return exists $self->{original} + ? $self->{original} + : exists $self->{qv} + ? $self->normal + : $self->numify; + } + + sub vcmp + { + require UNIVERSAL; + my ($left,$right,$swap) = @_; + my $class = ref($left); + unless ( UNIVERSAL::isa($right, $class) ) { + $right = $class->new($right); + } + + if ( $swap ) { + ($left, $right) = ($right, $left); + } + unless (_verify($left)) { + require Carp; + Carp::croak("Invalid version object"); + } + unless (_verify($right)) { + require Carp; + Carp::croak("Invalid version object"); + } + my $l = $#{$left->{version}}; + my $r = $#{$right->{version}}; + my $m = $l < $r ? $l : $r; + my $lalpha = $left->is_alpha; + my $ralpha = $right->is_alpha; + my $retval = 0; + my $i = 0; + while ( $i <= $m && $retval == 0 ) { + $retval = $left->{version}[$i] <=> $right->{version}[$i]; + $i++; + } + + # tiebreaker for alpha with identical terms + if ( $retval == 0 + && $l == $r + && $left->{version}[$m] == $right->{version}[$m] + && ( $lalpha || $ralpha ) ) { + + if ( $lalpha && !$ralpha ) { + $retval = -1; + } + elsif ( $ralpha && !$lalpha) { + $retval = +1; + } + } + + # possible match except for trailing 0's + if ( $retval == 0 && $l != $r ) { + if ( $l < $r ) { + while ( $i <= $r && $retval == 0 ) { + if ( $right->{version}[$i] != 0 ) { + $retval = -1; # not a match after all + } + $i++; + } + } + else { + while ( $i <= $l && $retval == 0 ) { + if ( $left->{version}[$i] != 0 ) { + $retval = +1; # not a match after all + } + $i++; + } + } + } + + return $retval; + } + + sub vbool { + my ($self) = @_; + return vcmp($self,$self->new("0"),1); + } + + sub vnoop { + require Carp; + Carp::croak("operation not supported with version object"); + } + + sub is_alpha { + my ($self) = @_; + return (exists $self->{alpha}); + } + + sub qv { + my $value = shift; + my $class = 'version'; + if (@_) { + $class = ref($value) || $value; + $value = shift; + } + + $value = _un_vstring($value); + $value = 'v'.$value unless $value =~ /(^v|\d+\.\d+\.\d)/; + my $version = $class->new($value); + return $version; + } + + *declare = \&qv; + + sub is_qv { + my ($self) = @_; + return (exists $self->{qv}); + } + + + sub _verify { + my ($self) = @_; + if ( ref($self) + && eval { exists $self->{version} } + && ref($self->{version}) eq 'ARRAY' + ) { + return 1; + } + else { + return 0; + } + } + + sub _is_non_alphanumeric { + my $s = shift; + $s = new charstar $s; + while ($s) { + return 0 if isSPACE($s); # early out + return 1 unless (isALPHA($s) || isDIGIT($s) || $s =~ /[.-]/); + $s++; + } + return 0; + } + + sub _un_vstring { + my $value = shift; + # may be a v-string + if ( length($value) >= 3 && $value !~ /[._]/ + && _is_non_alphanumeric($value)) { + my $tvalue; + if ( $] ge 5.008_001 ) { + $tvalue = _find_magic_vstring($value); + $value = $tvalue if length $tvalue; + } + elsif ( $] ge 5.006_000 ) { + $tvalue = sprintf("v%vd",$value); + if ( $tvalue =~ /^v\d+(\.\d+){2,}$/ ) { + # must be a v-string + $value = $tvalue; + } + } + } + return $value; + } + + sub _find_magic_vstring { + my $value = shift; + my $tvalue = ''; + require B; + my $sv = B::svref_2object(\$value); + my $magic = ref($sv) eq 'B::PVMG' ? $sv->MAGIC : undef; + while ( $magic ) { + if ( $magic->TYPE eq 'V' ) { + $tvalue = $magic->PTR; + $tvalue =~ s/^v?(.+)$/v$1/; + last; + } + else { + $magic = $magic->MOREMAGIC; + } + } + return $tvalue; + } + + sub _VERSION { + my ($obj, $req) = @_; + my $class = ref($obj) || $obj; + + no strict 'refs'; + if ( exists $INC{"$class.pm"} and not %{"$class\::"} and $] >= 5.008) { + # file but no package + require Carp; + Carp::croak( "$class defines neither package nor VERSION" + ."--version check failed"); + } + + my $version = eval "\$$class\::VERSION"; + if ( defined $version ) { + local $^W if $] <= 5.008; + $version = version::vpp->new($version); + } + + if ( defined $req ) { + unless ( defined $version ) { + require Carp; + my $msg = $] < 5.006 + ? "$class version $req required--this is only version " + : "$class does not define \$$class\::VERSION" + ."--version check failed"; + + if ( $ENV{VERSION_DEBUG} ) { + Carp::confess($msg); + } + else { + Carp::croak($msg); + } + } + + $req = version::vpp->new($req); + + if ( $req > $version ) { + require Carp; + if ( $req->is_qv ) { + Carp::croak( + sprintf ("%s version %s required--". + "this is only version %s", $class, + $req->normal, $version->normal) + ); + } + else { + Carp::croak( + sprintf ("%s version %s required--". + "this is only version %s", $class, + $req->stringify, $version->stringify) + ); + } + } + } + + return defined $version ? $version->stringify : undef; + } + + 1; #this line is important and will help the module return a true value +VERSION_VPP + +s/^ //mg for values %fatpacked; + +unshift @INC, sub { + if (my $fat = $fatpacked{$_[1]}) { + open my $fh, '<', \$fat + or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; + return $fh; + } + return +}; + +} # END OF FATPACK CODE + +use strict; +use App::cpanminus::script; + +unless (caller) { + my $app = App::cpanminus::script->new; + $app->parse_options(@ARGV); + $app->doit or exit(1); +} + +__END__ + +=head1 NAME + +cpanm - get, unpack build and install modules from CPAN + +=head1 SYNOPSIS + + cpanm Test::More # install Test::More + cpanm MIYAGAWA/Plack-0.99_05.tar.gz # full distribution path + cpanm http://example.org/LDS/CGI.pm-3.20.tar.gz # install from URL + cpanm ~/dists/MyCompany-Enterprise-1.00.tar.gz # install from a local file + cpanm --interactive Task::Kensho # Configure interactively + cpanm . # install from local directory + cpanm --installdeps . # install all the deps for the current directory + cpanm -L extlib Plack # install Plack and all non-core deps into extlib + cpanm --mirror http://cpan.cpantesters.org/ DBI # use the fast-syncing mirror + +=head1 COMMANDS + +=over 4 + +=item -i, --install + +Installs the modules. This is a default behavior and this is just a +compatibility option to make it work like L<cpan> or L<cpanp>. + +=item --self-upgrade + +Upgrades itself. It's just an alias for: + + cpanm App::cpanminus + +=item --info + +Displays the distribution information in +C<AUTHOR/Dist-Name-ver.tar.gz> format in the standard out. + +=item --installdeps + +Installs the dependencies of the target distribution but won't build +itself. Handy if you want to try the application from a version +controlled repository such as git. + + cpanm --installdeps . + +=item --look + +Download and unpack the distribution and then open the directory with +your shell. Handy to poke around the source code or do the manual +testing. + +=item -h, --help + +Displays the help message. + +=item -V, --version + +Displays the version number. + +=back + +=head1 OPTIONS + +You can specify the default options in C<PERL_CPANM_OPT> environment variable. + +=over 4 + +=item -f, --force + +Force install modules even when testing failed. + +=item -n, --notest + +Skip the testing of modules. Use this only when you just want to save +time for installing hundreds of distributions to the same perl and +architecture you've already tested to make sure it builds fine. + +Defaults to false, and you can say C<--no-notest> to override when it +is set in the default options in C<PERL_CPANM_OPT>. + +=item -S, --sudo + +Switch to the root user with C<sudo> when installing modules. Use this +if you want to install modules to the system perl include path. + +Defaults to false, and you can say C<--no-sudo> to override when it is +set in the default options in C<PERL_CPANM_OPT>. + +=item -v, --verbose + +Makes the output verbose. It also enables the interactive +configuration. (See --interactive) + +=item -q, --quiet + +Makes the output even more quiet than the default. It doesn't print +anything to the STDERR. + +=item -l, --local-lib + +Sets the L<local::lib> compatible path to install modules to. You +don't need to set this if you already configure the shell environment +variables using L<local::lib>, but this can be used to override that +as well. + +=item -L, --local-lib-contained + +Same with C<--local-lib> but when examining the dependencies, it +assumes no non-core modules are installed on the system. It's handy if +you want to bundle application dependencies in one directory so you +can distribute to other machines. + +For instance, + + cpanm -L extlib Plack + +would install Plack and all of its non-core dependencies into the +directory C<extlib>, which can be loaded from your application with: + + use local::lib '/path/to/extlib'; + +=item --mirror + +Specifies the base URL for the CPAN mirror to use, such as +C<http://cpan.cpantesters.org/> (you can omit the trailing slash). You +can specify multiple mirror URLs by repeating the command line option. + +Defaults to C<http://search.cpan.org/CPAN> which is a geo location +aware redirector. + +=item --mirror-only + +Download the mirror's 02packages.details.txt.gz index file instead of +querying the CPAN Meta DB. + +Select this option if you are using a local mirror of CPAN, such as +minicpan when you're offline, or your own CPAN index (a.k.a darkpan). + +B<Tip:> It might be useful if you name these mirror options with your +shell aliases, like: + + alias minicpanm='cpanm --mirror ~/minicpan --mirror-only' + alias darkpan='cpanm --mirror http://mycompany.example.com/DPAN --mirror-only' + +=item --prompt + +Prompts when a test fails so that you can skip, force install, retry +or look in the shell to see what's going wrong. It also prompts when +one of the dependency failed if you want to proceed the installation. + +Defaults to false, and you can say C<--no-prompt> to override if it's +set in the default options in C<PERL_CPANM_OPT>. + +=item --reinstall + +cpanm, when given a module name in the command line (i.e. C<cpanm +Plack>), checks the locally installed version first and skips if it is +already installed. This option makes it skip the check, so: + + cpanm --reinstall Plack + +would reinstall L<Plack> even if your locally installed version is +latest, or even newer (which would happen if you install a developer +release from version control repositories). + +Defaults to false. + +=item --interactive + +Makes the configuration (such as C<Makefile.PL> and C<Build.PL>) +interactive, so you can answer questions in the distribution that +requires custom configuration or Task:: distributions. + +Defaults to false, and you can say C<--no-interactive> to override +when it's set in the default options in C<PERL_CPANM_OPT>. + +=item --uninst-shadows + +Uninstalls the shadow files of the distribution that you're +installing. This eliminates the confusion if you're trying to install +core (dual-life) modules from CPAN against perl 5.10 or older, or +modules that used to be XS-based but switched to pure perl at some +version. + +If you run cpanm as root and use C<INSTALL_BASE> or equivalent to +specify custom installation path, you SHOULD disable this option so +you won't accidentally uninstall dual-life modules from the core +include path. + +Defaults to true if your perl version is smaller than 5.12, and you +can disable that with C<--no-uninst-shadows>. + +B<NOTE>: Since version 1.3000 this flag is turned off by default for +perl newer than 5.12, since with 5.12 @INC contains site_perl directory +I<before> the perl core library path, and uninstalling shadows is not +necessary anymore and does more harm by deleting files from the core +library path. + +=item --auto-cleanup + +Specifies the number of days in whcih cpanm's work directories +expire. Defaults to 7, which means old work directories will be +cleaned up in one week. + +You can set the value to C<0> to make cpan never cleanup those +directories. + +=item --lwp + +Uses L<LWP> module to download stuff over HTTP. Defaults to true, and +you can say C<--no-lwp> to disable using LWP, when you want to upgrade +LWP from CPAN on some broken perl systems. + +=item --wget + +Uses GNU Wget (if available) to download stuff. Defaults to true, and +you can say C<--no-wget> to disable using Wget (versions of Wget older +than 1.9 don't support the C<--retry-connrefused> option used by cpanm). + +=item --curl + +Uses cURL (if available) to download stuff. Defaults to true, and +you can say C<--no-curl> to disable using cURL. + +Normally with C<--lwp>, C<--wget> and C<--curl> options set to true +(which is the default) cpanm tries L<LWP>, Wget, cURL and L<HTTP::Tiny> +(in that order) and uses the first one available. + +=back + +=head1 SEE ALSO + +L<App::cpanminus> + +=head1 COPYRIGHT + +Copyright 2010 Tatsuhiko Miyagawa. + +=head1 AUTHOR + +Tatsuhiko Miyagawa + +=cut diff --git a/perl-external/dpan_config b/perl-external/dpan_config new file mode 100644 index 000000000..cd0d0b203 --- /dev/null +++ b/perl-external/dpan_config @@ -0,0 +1,2 @@ +extra_reports_dir extra_reports +skip_dists_regexes common-sense
\ No newline at end of file diff --git a/perl-external/dpan_l4p_config b/perl-external/dpan_l4p_config new file mode 100644 index 000000000..883372464 --- /dev/null +++ b/perl-external/dpan_l4p_config @@ -0,0 +1,4 @@ +log4perl.rootLogger = DEBUG, Screen + +log4perl.appender.Screen = Log::Log4perl::Appender::Screen +log4perl.appender.Screen.layout = Log::Log4perl::Layout::SimpleLayout diff --git a/perl-external/minicpan/.gitignore b/perl-external/minicpan/.gitignore new file mode 100644 index 000000000..0f6f4afed --- /dev/null +++ b/perl-external/minicpan/.gitignore @@ -0,0 +1 @@ +authors diff --git a/perl-external/minicpan/extra_reports/common-sense-3.4.txt b/perl-external/minicpan/extra_reports/common-sense-3.4.txt new file mode 100644 index 000000000..6d39e3a0f --- /dev/null +++ b/perl-external/minicpan/extra_reports/common-sense-3.4.txt @@ -0,0 +1,2 @@ +# Primary package [TAB] version [TAB] dist file [newline] +common::sense 3.4 authors/id/M/ML/MLEHMANN/common-sense-3.4.tar.gz
\ No newline at end of file diff --git a/perl-external/minicpan/modules/02packages.details.txt.gz b/perl-external/minicpan/modules/02packages.details.txt.gz Binary files differnew file mode 100644 index 000000000..49329fce5 --- /dev/null +++ b/perl-external/minicpan/modules/02packages.details.txt.gz diff --git a/perl-external/minicpan/modules/03modlist.data.gz b/perl-external/minicpan/modules/03modlist.data.gz Binary files differnew file mode 100644 index 000000000..9c8cc712e --- /dev/null +++ b/perl-external/minicpan/modules/03modlist.data.gz diff --git a/perl-external/modules.txt b/perl-external/modules.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/perl-external/modules.txt diff --git a/perl-external/urls.txt b/perl-external/urls.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/perl-external/urls.txt |