Add CPANPLUS::Dist::Build to the core
authorJos I. Boumans <kane@dwim.org>
Fri, 4 May 2007 14:41:39 +0000 (16:41 +0200)
committerRafael Garcia-Suarez <rgarciasuarez@gmail.com>
Fri, 4 May 2007 14:33:07 +0000 (14:33 +0000)
From: "Jos I. Boumans" <kane@dwim.org>
Message-Id: <58AAEC18-D5B6-4840-9FA5-B121D95446A3@dwim.org>

p4raw-id: //depot/perl@31140

MANIFEST
lib/CPANPLUS/Dist/Build.pm [new file with mode: 0644]
lib/CPANPLUS/Dist/Build/Constants.pm [new file with mode: 0644]
lib/CPANPLUS/Dist/Build/t/01_CPANPLUS-Dist-Build-Constants.t [new file with mode: 0644]
lib/CPANPLUS/Dist/Build/t/02_CPANPLUS-Dist-Build.t [new file with mode: 0644]
lib/CPANPLUS/Dist/Build/t/inc/conf.pl [new file with mode: 0644]
lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed [new file with mode: 0644]
lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed [new file with mode: 0644]

index 3bbc319..255c2a7 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -1558,6 +1558,13 @@ lib/CPANPLUS/Config.pm   CPANPLUS
 lib/CPANPLUS/Configure.pm      CPANPLUS
 lib/CPANPLUS/Configure/Setup.pm        CPANPLUS
 lib/CPANPLUS/Dist/Base.pm      CPANPLUS
+lib/CPANPLUS/Dist/Build/Constants.pm   CPANPLUS::Dist::Build
+lib/CPANPLUS/Dist/Build.pm     CPANPLUS::Dist::Build
+lib/CPANPLUS/Dist/Build/t/01_CPANPLUS-Dist-Build-Constants.t   CPANPLUS::Dist::Build tests
+lib/CPANPLUS/Dist/Build/t/02_CPANPLUS-Dist-Build.t     CPANPLUS::Dist::Build tests
+lib/CPANPLUS/Dist/Build/t/inc/conf.pl  CPANPLUS::Dist::Build tests
+lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed  CPANPLUS::Dist::Build tests
+lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed    CPANPLUS::Dist::Build tests
 lib/CPANPLUS/Dist/MM.pm        CPANPLUS
 lib/CPANPLUS/Dist.pm   CPANPLUS
 lib/CPANPLUS/Dist/Sample.pm    CPANPLUS
@@ -2690,11 +2697,11 @@ lib/Text/TabsWrap/t/tabs.t      See if Text::Tabs works
 lib/Text/TabsWrap/t/wrap.t     See if Text::Wrap::wrap works
 lib/Text/Wrap.pm               Paragraph formatter
 lib/Thread.pm                  Thread extensions frontend
-lib/Thread.t                   Thread extensions frontend tests
 lib/Thread/Queue.pm            Threadsafe queue
 lib/Thread/Queue.t             See if threadsafe queue works
 lib/Thread/Semaphore.pm                Threadsafe semaphore
 lib/Thread/Semaphore.t         See if threadsafe semaphore works
+lib/Thread.t                   Thread extensions frontend tests
 lib/Tie/Array.pm               Base class for tied arrays
 lib/Tie/Array/push.t           Test for Tie::Array
 lib/Tie/Array/splice.t         Test for Tie::Array::SPLICE
@@ -3197,8 +3204,8 @@ Porting/p4genpatch        Generate patch from p4 change in repository (obsoletes p4desc
 Porting/patching.pod   How to report changes made to Perl
 Porting/patchls                Flexible patch file listing utility
 Porting/pumpkin.pod    Guidelines and hints for Perl maintainers
-Porting/repository.pod How to use the Perl repository
 Porting/regcharclass.pl        Generate regcharclass.h from inline data
+Porting/repository.pod How to use the Perl repository
 Porting/sort_perldiag.pl       Keep our diagnostics orderly
 Porting/testall.atom           Cumulative profile with Third Degree
 Porting/thirdclean             Cleanup Third Degree reports
@@ -3260,11 +3267,11 @@ README.win32                    Perl notes for Windows
 reentr.c                       Reentrant interfaces
 reentr.h                       Reentrant interfaces
 reentr.pl                      Reentrant interfaces
+regcharclass.h                 Generated by Porting/regcharclass.pl
 regcomp.c                      Regular expression compiler
 regcomp.h                      Private declarations for above
 regcomp.pl                     Builder of regnodes.h
 regcomp.sym                    Data for regnodes.h
-regcharclass.h                 Generated by Porting/regcharclass.pl
 regen_lib.pl                   Common file routines for generator scripts
 regen_perly.pl                 generate perly.{act,h,tab} from perly.y
 regen.pl                       Run all scripts that (re)generate files
@@ -3613,9 +3620,9 @@ t/mro/method_caching.t            mro tests
 t/mro/next_edgecases.t         mro tests
 t/mro/next_inanon.t            mro tests
 t/mro/next_ineval.t            mro tests
-t/mro/next_skip.t              mro tests
 t/mro/next_method.t            mro tests
 t/mro/next_NEXT.t              mro tests
+t/mro/next_skip.t              mro tests
 t/mro/overload_c3.t            mro tests
 t/mro/overload_dfs.t           mro tests
 t/mro/recursion_c3.t           mro tests
diff --git a/lib/CPANPLUS/Dist/Build.pm b/lib/CPANPLUS/Dist/Build.pm
new file mode 100644 (file)
index 0000000..4d4aec0
--- /dev/null
@@ -0,0 +1,791 @@
+package CPANPLUS::Dist::Build;
+
+use strict;
+use vars    qw[@ISA $STATUS $VERSION];
+@ISA =      qw[CPANPLUS::Dist];
+
+use CPANPLUS::inc;
+use CPANPLUS::Internals::Constants;
+
+### these constants were exported by CPANPLUS::Internals::Constants
+### in previous versions.. they do the same though. If we want to have
+### a normal 'use' here, up the dependency to CPANPLUS 0.056 or higher
+BEGIN { 
+    require CPANPLUS::Dist::Build::Constants;
+    CPANPLUS::Dist::Build::Constants->import()
+        if not __PACKAGE__->can('BUILD') && __PACKAGE__->can('BUILD_DIR');
+}
+
+use CPANPLUS::Error;
+
+use Config;
+use FileHandle;
+use Cwd;
+
+use IPC::Cmd                    qw[run];
+use Params::Check               qw[check];
+use Module::Load::Conditional   qw[can_load check_install];
+use Locale::Maketext::Simple    Class => 'CPANPLUS', Style => 'gettext';
+
+local $Params::Check::VERBOSE = 1;
+
+$VERSION = '0.06';
+
+=pod
+
+=head1 NAME
+
+CPANPLUS::Dist::Build
+
+=head1 SYNOPSIS
+
+    my $build = CPANPLUS::Dist->new(
+                                format  => 'CPANPLUS::Dist::Build',
+                                module  => $modobj,
+                            );
+                            
+    $build->prepare;    # runs Module::Build->new_from_context;                            
+    $build->create;     # runs build && build test
+    $build->install;    # runs build install
+
+
+=head1 DESCRIPTION
+
+C<CPANPLUS::Dist::Build> is a distribution class for C<Module::Build>
+related modules.
+Using this package, you can create, install and uninstall perl
+modules. It inherits from C<CPANPLUS::Dist>.
+
+Normal users won't have to worry about the interface to this module,
+as it functions transparently as a plug-in to C<CPANPLUS> and will 
+just C<Do The Right Thing> when it's loaded.
+
+=head1 ACCESSORS
+
+=over 4
+
+=item parent()
+
+Returns the C<CPANPLUS::Module> object that parented this object.
+
+=item status()
+
+Returns the C<Object::Accessor> object that keeps the status for
+this module.
+
+=back
+
+=head1 STATUS ACCESSORS
+
+All accessors can be accessed as follows:
+    $build->status->ACCESSOR
+
+=over 4
+
+=item build_pl ()
+
+Location of the Build file.
+Set to 0 explicitly if something went wrong.
+
+=item build ()
+
+BOOL indicating if the C<Build> command was successful.
+
+=item test ()
+
+BOOL indicating if the C<Build test> command was successful.
+
+=item prepared ()
+
+BOOL indicating if the C<prepare> call exited succesfully
+This gets set after C<perl Build.PL>
+
+=item distdir ()
+
+Full path to the directory in which the C<prepare> call took place,
+set after a call to C<prepare>. 
+
+=item created ()
+
+BOOL indicating if the C<create> call exited succesfully. This gets
+set after C<Build> and C<Build test>.
+
+=item installed ()
+
+BOOL indicating if the module was installed. This gets set after
+C<Build install> exits successfully.
+
+=item uninstalled ()
+
+BOOL indicating if the module was uninstalled properly.
+
+=item _create_args ()
+
+Storage of the arguments passed to C<create> for this object. Used
+for recursive calls when satisfying prerequisites.
+
+=item _install_args ()
+
+Storage of the arguments passed to C<install> for this object. Used
+for recursive calls when satisfying prerequisites.
+
+=item _mb_object ()
+
+Storage of the C<Module::Build> object we used for this installation.
+
+=back
+
+=cut
+
+
+=head1 METHODS
+
+=head2 $bool = CPANPLUS::Dist::Build->format_available();
+
+Returns a boolean indicating whether or not you can use this package
+to create and install modules in your environment.
+
+=cut
+
+### check if the format is available ###
+sub format_available {
+    my $mod = "Module::Build";
+    unless( can_load( modules => { $mod => '0.2611' } ) ) {
+        error( loc( "You do not have '%1' -- '%2' not available",
+                    $mod, __PACKAGE__ ) );
+        return;
+    }
+
+    return 1;
+}
+
+
+=head2 $bool = $dist->init();
+
+Sets up the C<CPANPLUS::Dist::Build> object for use.
+Effectively creates all the needed status accessors.
+
+Called automatically whenever you create a new C<CPANPLUS::Dist> object.
+
+=cut
+
+sub init {
+    my $dist    = shift;
+    my $status  = $dist->status;
+
+    $status->mk_accessors(qw[build_pl build test created installed uninstalled
+                             _create_args _install_args _prepare_args
+                             _mb_object _buildflags
+                            ]);
+
+    ### just in case 'format_available' didn't get called
+    require Module::Build;
+
+    return 1;
+}
+
+=pod
+
+=head2 $bool = $dist->prepare([perl => '/path/to/perl', buildflags => 'EXTRA=FLAGS', force => BOOL, verbose => BOOL])
+
+C<prepare> prepares a distribution, running C<Module::Build>'s 
+C<new_from_context> method, and establishing any prerequisites this
+distribution has.
+
+When running C<< Module::Build->new_from_context >>, the environment 
+variable C<PERL5_CPANPLUS_IS_EXECUTING> will be set to the full path 
+of the C<Build.PL> that is being executed. This enables any code inside
+the C<Build.PL> to know that it is being installed via CPANPLUS.
+
+After a succcesfull C<prepare> you may call C<create> to create the
+distribution, followed by C<install> to actually install it.
+
+Returns true on success and false on failure.
+
+=cut
+
+sub prepare {
+    ### just in case you already did a create call for this module object
+    ### just via a different dist object
+    my $dist = shift;
+    my $self = $dist->parent;
+
+    ### we're also the cpan_dist, since we don't need to have anything
+    ### prepared from another installer
+    $dist    = $self->status->dist_cpan if      $self->status->dist_cpan;
+    $self->status->dist_cpan( $dist )   unless  $self->status->dist_cpan;
+
+    my $cb   = $self->parent;
+    my $conf = $cb->configure_object;
+    my %hash = @_;
+
+    my $dir;
+    unless( $dir = $self->status->extract ) {
+        error( loc( "No dir found to operate on!" ) );
+        return;
+    }
+
+    my $args;
+    my( $force, $verbose, $buildflags, $perl);
+    {   local $Params::Check::ALLOW_UNKNOWN = 1;
+        my $tmpl = {
+            force           => {    default => $conf->get_conf('force'),
+                                    store   => \$force },
+            verbose         => {    default => $conf->get_conf('verbose'),
+                                    store   => \$verbose },
+            perl            => {    default => $^X, store => \$perl },
+            buildflags      => {    default => $conf->get_conf('buildflags'),
+                                    store   => \$buildflags },
+        };
+
+        $args = check( $tmpl, \%hash ) or return;
+    }
+
+    return 1 if $dist->status->prepared && !$force;
+
+    $dist->status->_prepare_args( $args );
+
+    ### chdir to work directory ###
+    my $orig = cwd();
+    unless( $cb->_chdir( dir => $dir ) ) {
+        error( loc( "Could not chdir to build directory '%1'", $dir ) );
+        return;
+    }
+
+    ### by now we've loaded module::build, and we're using the API, so
+    ### it's safe to remove CPANPLUS::inc from our inc path, especially
+    ### because it can trip up tests run under taint (just like EU::MM).
+    ### turn off our PERL5OPT so no modules from CPANPLUS::inc get
+    ### included in make test -- it should build without.
+    ### also, modules that run in taint mode break if we leave
+    ### our code ref in perl5opt
+    ### XXX we've removed the ENV settings from cp::inc, so only need
+    ### to reset the @INC
+    #local $ENV{PERL5OPT} = CPANPLUS::inc->original_perl5opt;
+    #local $ENV{PERL5LIB} = CPANPLUS::inc->original_perl5lib;
+    local @INC           = CPANPLUS::inc->original_inc;
+
+    ### this will generate warnings under anything lower than M::B 0.2606
+    my %buildflags = $dist->_buildflags_as_hash( $buildflags );
+    $dist->status->_buildflags( $buildflags );
+
+    my $fail;
+    RUN: {
+        # Wrap the exception that may be thrown here (should likely be
+        # done at a much higher level).
+        my $mb = eval { 
+            my $env = 'ENV_CPANPLUS_IS_EXECUTING';
+            local $ENV{$env} = BUILD_PL->( $dir );
+            Module::Build->new_from_context( %buildflags ) 
+        };
+        if( !$mb or $@ ) {
+            error(loc("Could not create Module::Build object: %1","$@"));
+            $fail++; last RUN;
+        }
+
+        $dist->status->_mb_object( $mb );
+
+        $self->status->prereqs( $dist->_find_prereqs( verbose => $verbose ) );
+
+    }
+    
+    ### send out test report? ###
+    if( $fail and $conf->get_conf('cpantest') ) {
+           $cb->_send_report( 
+            module  => $self,
+            failed  => $fail,
+            buffer  => CPANPLUS::Error->stack_as_string,
+            verbose => $verbose,
+            force   => $force,
+        ) or error(loc("Failed to send test report for '%1'",
+                    $self->module ) );
+    }
+
+    unless( $cb->_chdir( dir => $orig ) ) {
+        error( loc( "Could not chdir back to start dir '%1'", $orig ) );
+    }
+
+    ### save where we wrote this stuff -- same as extract dir in normal
+    ### installer circumstances
+    $dist->status->distdir( $self->status->extract );
+
+    return $dist->status->prepared( $fail ? 0 : 1 );
+}
+
+sub _find_prereqs {
+    my $dist = shift;
+    my $mb   = $dist->status->_mb_object;
+    my $self = $dist->parent;
+    my $cb   = $self->parent;
+
+    my $prereqs = {};
+    foreach my $type ('requires', 'build_requires') {
+      my $p = $mb->$type() || {};
+      $prereqs->{$_} = $p->{$_} foreach keys %$p;
+    }
+
+    ### allows for a user defined callback to filter the prerequisite
+    ### list as they see fit, to remove (or add) any prereqs they see
+    ### fit. The default installed callback will return the hashref in
+    ### an unmodified form
+    ### this callback got added after cpanplus 0.0562, so use a 'can'
+    ### to find out if it's supported. For older versions, we'll just
+    ### return the hashref as is ourselves.
+    my $href    = $cb->_callbacks->can('filter_prereqs')
+                    ? $cb->_callbacks->filter_prereqs->( $cb, $prereqs )
+                    : $prereqs;
+
+    $self->status->prereqs( $href );
+
+    ### make sure it's not the same ref
+    return { %$href };
+}
+
+sub prereq_satisfied {
+  # Return true if this prereq is satisfied.  Return false if it's
+  # not.  Also issue an error if the latest CPAN version doesn't
+  # satisfy it.
+  
+  my ($dist, %args) = @_;
+  my $mb   = $dist->status->_mb_object;
+  my $cb   = $dist->parent->parent;
+  my $mod = $args{modobj}->module;
+  
+  my $status = $mb->check_installed_status($mod, $args{version});
+  return 1 if $status->{ok};
+  
+  # Check the latest version from the CPAN index
+  {
+    no strict 'refs';
+    local ${$mod . '::VERSION'} = $args{modobj}->version;
+    $status = $mb->check_installed_status($mod, $args{version});
+  }
+  unless( $status->{ok} ) {
+    error(loc("This distribution depends on $mod, but the latest version of $mod on CPAN ".
+             "doesn't satisfy the specific version dependency ($args{version}). ".
+             "Please try to resolve this dependency manually."));
+  }
+  
+  return 0;
+}
+
+=pod
+
+=head2 $dist->create([perl => '/path/to/perl', buildflags => 'EXTRA=FLAGS', prereq_target => TARGET, force => BOOL, verbose => BOOL, skiptest => BOOL])
+
+C<create> preps a distribution for installation. This means it will
+run C<Build> and C<Build test>, via the C<Module::Build> API.
+This will also satisfy any prerequisites the module may have.
+
+If you set C<skiptest> to true, it will skip the C<Build test> stage.
+If you set C<force> to true, it will go over all the stages of the
+C<Build> process again, ignoring any previously cached results. It
+will also ignore a bad return value from C<Build test> and still allow
+the operation to return true.
+
+Returns true on success and false on failure.
+
+You may then call C<< $dist->install >> on the object to actually
+install it.
+
+=cut
+
+sub create {
+    ### just in case you already did a create call for this module object
+    ### just via a different dist object
+    my $dist = shift;
+    my $self = $dist->parent;
+
+    ### we're also the cpan_dist, since we don't need to have anything
+    ### prepared from another installer
+    $dist    = $self->status->dist_cpan if      $self->status->dist_cpan;
+    $self->status->dist_cpan( $dist )   unless  $self->status->dist_cpan;
+
+    my $cb   = $self->parent;
+    my $conf = $cb->configure_object;
+    my $mb   = $dist->status->_mb_object;
+    my %hash = @_;
+
+    my $dir;
+    unless( $dir = $self->status->extract ) {
+        error( loc( "No dir found to operate on!" ) );
+        return;
+    }
+
+    my $args;
+    my( $force, $verbose, $buildflags, $skiptest, $prereq_target,
+        $perl, $prereq_format, $prereq_build);
+    {   local $Params::Check::ALLOW_UNKNOWN = 1;
+        my $tmpl = {
+            force           => {    default => $conf->get_conf('force'),
+                                    store   => \$force },
+            verbose         => {    default => $conf->get_conf('verbose'),
+                                    store   => \$verbose },
+            perl            => {    default => $^X, store => \$perl },
+            buildflags      => {    default => $conf->get_conf('buildflags'),
+                                    store   => \$buildflags },
+            skiptest        => {    default => $conf->get_conf('skiptest'),
+                                    store   => \$skiptest },
+            prereq_target   => {    default => '', store => \$prereq_target },
+            ### don't set the default format to 'build' -- that is wrong!
+            prereq_format   => {    #default => $self->status->installer_type,
+                                    default => '',
+                                    store   => \$prereq_format },
+            prereq_build    => {    default => 0, store => \$prereq_build },                                    
+        };
+
+        $args = check( $tmpl, \%hash ) or return;
+    }
+
+    return 1 if $dist->status->created && !$force;
+
+    $dist->status->_create_args( $args );
+
+    ### is this dist prepared?
+    unless( $dist->status->prepared ) {
+        error( loc( "You have not successfully prepared a '%2' distribution ".
+                    "yet -- cannot create yet", __PACKAGE__ ) );
+        return;
+    }
+
+    ### chdir to work directory ###
+    my $orig = cwd();
+    unless( $cb->_chdir( dir => $dir ) ) {
+        error( loc( "Could not chdir to build directory '%1'", $dir ) );
+        return;
+    }
+
+    ### by now we've loaded module::build, and we're using the API, so
+    ### it's safe to remove CPANPLUS::inc from our inc path, especially
+    ### because it can trip up tests run under taint (just like EU::MM).
+    ### turn off our PERL5OPT so no modules from CPANPLUS::inc get
+    ### included in make test -- it should build without.
+    ### also, modules that run in taint mode break if we leave
+    ### our code ref in perl5opt
+    ### XXX we've removed the ENV settings from cp::inc, so only need
+    ### to reset the @INC
+    #local $ENV{PERL5OPT} = CPANPLUS::inc->original_perl5opt;
+    #local $ENV{PERL5LIB} = CPANPLUS::inc->original_perl5lib;
+    local @INC           = CPANPLUS::inc->original_inc;
+
+    ### but do it *before* the new_from_context, as M::B seems
+    ### to be actually running the file...
+    ### an unshift in the block seems to be ignored.. somehow...
+    #{   my $lib = $self->best_path_to_module_build;
+    #    unshift @INC, $lib if $lib;
+    #}
+    unshift @INC, $self->best_path_to_module_build
+                if $self->best_path_to_module_build;
+
+    ### this will generate warnings under anything lower than M::B 0.2606
+    my %buildflags = $dist->_buildflags_as_hash( $buildflags );
+    $dist->status->_buildflags( $buildflags );
+
+    my $fail; my $prereq_fail; my $test_fail;
+    RUN: {
+
+        ### this will set the directory back to the start
+        ### dir, so we must chdir /again/
+        my $ok = $dist->_resolve_prereqs(
+                        force           => $force,
+                        format          => $prereq_format,
+                        verbose         => $verbose,
+                        prereqs         => $self->status->prereqs,
+                        target          => $prereq_target,
+                        prereq_build    => $prereq_build,
+                    );
+
+        unless( $cb->_chdir( dir => $dir ) ) {
+            error( loc( "Could not chdir to build directory '%1'", $dir ) );
+            return;
+        }
+
+        unless( $ok ) {
+            #### use $dist->flush to reset the cache ###
+            error( loc( "Unable to satisfy prerequisites for '%1' " .
+                        "-- aborting install", $self->module ) );
+            $dist->status->build(0);
+            $fail++; $prereq_fail++;
+            last RUN;
+        }
+
+        eval { $mb->dispatch('build', %buildflags) };
+        if( $@ ) {
+            error(loc("Could not run '%1': %2", 'Build', "$@"));
+            $dist->status->build(0);
+            $fail++; last RUN;
+        }
+
+        $dist->status->build(1);
+
+        ### add this directory to your lib ###
+        $cb->_add_to_includepath(
+            directories => [ BLIB_LIBDIR->( $self->status->extract ) ]
+        );
+
+        ### this buffer will not include what tests failed due to a 
+        ### M::B/Test::Harness bug. Reported as #9793 with patch 
+        ### against 0.2607 on 26/1/2005
+        unless( $skiptest ) {
+            eval { $mb->dispatch('test', %buildflags) };
+            if( $@ ) {
+                error(loc("Could not run '%1': %2", 'Build test', "$@"));
+
+                ### mark specifically *test* failure.. so we dont
+                ### send success on force...
+                $test_fail++;
+
+                unless($force) {
+                    $dist->status->test(0);
+                    $fail++; last RUN;
+                }
+            } else {
+                $dist->status->test(1);
+            }
+        } else {
+            msg(loc("Tests skipped"), $verbose);
+        }            
+    }
+
+    unless( $cb->_chdir( dir => $orig ) ) {
+        error( loc( "Could not chdir back to start dir '%1'", $orig ) );
+    }
+
+    ### send out test report? ###
+    if( $conf->get_conf('cpantest') and not $prereq_fail ) {
+        $cb->_send_report(
+            module          => $self,
+            failed          => $test_fail || $fail,
+            buffer          => CPANPLUS::Error->stack_as_string,
+            verbose         => $verbose,
+            force           => $force,
+            tests_skipped   => $skiptest,
+        ) or error(loc("Failed to send test report for '%1'",
+                    $self->module ) );
+    }
+
+    return $dist->status->created( $fail ? 0 : 1 );
+}
+
+=head2 $dist->install([verbose => BOOL, perl => /path/to/perl])
+
+Actually installs the created dist.
+
+Returns true on success and false on failure.
+
+=cut
+
+sub install {
+    ### just in case you already did a create call for this module object
+    ### just via a different dist object
+    my $dist = shift;
+    my $self = $dist->parent;
+
+    ### we're also the cpan_dist, since we don't need to have anything
+    ### prepared from another installer
+    $dist    = $self->status->dist_cpan if $self->status->dist_cpan;
+    my $mb   = $dist->status->_mb_object;
+
+    my $cb   = $self->parent;
+    my $conf = $cb->configure_object;
+    my %hash = @_;
+
+    
+    my $verbose; my $perl; my $force;
+    {   local $Params::Check::ALLOW_UNKNOWN = 1;
+        my $tmpl = {
+            verbose => { default => $conf->get_conf('verbose'),
+                         store   => \$verbose },
+            force   => { default => $conf->get_conf('force'),
+                         store   => \$force },
+            perl    => { default => $^X, store   => \$perl },
+        };
+    
+        my $args = check( $tmpl, \%hash ) or return;
+        $dist->status->_install_args( $args );
+    }
+
+    my $dir;
+    unless( $dir = $self->status->extract ) {
+        error( loc( "No dir found to operate on!" ) );
+        return;
+    }
+
+    my $orig = cwd();
+
+    unless( $cb->_chdir( dir => $dir ) ) {
+        error( loc( "Could not chdir to build directory '%1'", $dir ) );
+        return;
+    }
+
+    ### value set and false -- means failure ###
+    if( defined $self->status->installed && 
+        !$self->status->installed && !$force
+    ) {
+        error( loc( "Module '%1' has failed to install before this session " .
+                    "-- aborting install", $self->module ) );
+        return;
+    }
+
+    my $fail;
+    my $buildflags = $dist->status->_buildflags;
+    ### hmm, how is this going to deal with sudo?
+    ### for now, check effective uid, if it's not root,
+    ### shell out, otherwise use the method
+    if( $> ) {
+
+        ### don't worry about loading the right version of M::B anymore
+        ### the 'new_from_context' already added the 'right' path to
+        ### M::B at the top of the build.pl
+        my $cmd     = [$perl, BUILD->($dir), 'install', $buildflags];
+        my $sudo    = $conf->get_program('sudo');
+        unshift @$cmd, $sudo if $sudo;
+
+
+        my $buffer;
+        unless( scalar run( command => $cmd,
+                            buffer  => \$buffer,
+                            verbose => $verbose )
+        ) {
+            error(loc("Could not run '%1': %2", 'Build install', $buffer));
+            $fail++;
+        }
+    } else {
+        my %buildflags = $dist->_buildflags_as_hash($buildflags);
+
+        eval { $mb->dispatch('install', %buildflags) };
+        if( $@ ) {
+            error(loc("Could not run '%1': %2", 'Build install', "$@"));
+            $fail++;
+        }
+    }
+
+
+    unless( $cb->_chdir( dir => $orig ) ) {
+        error( loc( "Could not chdir back to start dir '%1'", $orig ) );
+    }
+
+    return $dist->status->installed( $fail ? 0 : 1 );
+}
+
+### returns the string 'foo=bar zot=quux' as (foo => bar, zot => quux)
+sub _buildflags_as_hash {
+    my $self    = shift;
+    my $flags   = shift or return;
+
+    my @argv    = Module::Build->split_like_shell($flags);
+    my ($argv)  = Module::Build->read_args(@argv);
+
+    return %$argv;
+}
+
+
+sub dist_dir {
+    ### just in case you already did a create call for this module object
+    ### just via a different dist object
+    my $dist = shift;
+    my $self = $dist->parent;
+
+    ### we're also the cpan_dist, since we don't need to have anything
+    ### prepared from another installer
+    $dist    = $self->status->dist_cpan if $self->status->dist_cpan;
+    my $mb   = $dist->status->_mb_object;
+
+    my $cb   = $self->parent;
+    my $conf = $cb->configure_object;
+    my %hash = @_;
+
+    
+    my $dir;
+    unless( $dir = $self->status->extract ) {
+        error( loc( "No dir found to operate on!" ) );
+        return;
+    }
+    
+    ### chdir to work directory ###
+    my $orig = cwd();
+    unless( $cb->_chdir( dir => $dir ) ) {
+        error( loc( "Could not chdir to build directory '%1'", $dir ) );
+        return;
+    }
+
+    my $fail; my $distdir;
+    TRY: {    
+        $dist->prepare( @_ ) or (++$fail, last TRY);
+
+
+        eval { $mb->dispatch('distdir') };
+        if( $@ ) {
+            error(loc("Could not run '%1': %2", 'Build distdir', "$@"));
+            ++$fail, last TRY;
+        }
+
+        ### /path/to/Foo-Bar-1.2/Foo-Bar-1.2
+        $distdir = File::Spec->catdir( $dir, $self->package_name . '-' .
+                                                $self->package_version );
+
+        unless( -d $distdir ) {
+            error(loc("Do not know where '%1' got created", 'distdir'));
+            ++$fail, last TRY;
+        }
+    }
+
+    unless( $cb->_chdir( dir => $orig ) ) {
+        error( loc( "Could not chdir to start directory '%1'", $orig ) );
+        return;
+    }
+
+    return if $fail;
+    return $distdir;
+}    
+
+=head1 KNOWN ISSUES
+
+Below are some of the known issues with Module::Build, that we hope 
+the authors will resolve at some point, so we can make full use of
+Module::Build's power. 
+The number listed is the bug number on C<rt.cpan.org>.
+
+=over 4
+
+=item * Module::Build can not be upgraded using its own API (#13169)
+
+This is due to the fact that the Build file insists on adding a path
+to C<@INC> which force the loading of the C<not yet installed>
+Module::Build when it shells out to run it's own build procedure:
+
+=item * Module::Build does not provide access to install history (#9793)
+
+C<Module::Build> runs the create, test and install procedures in it's
+own processes, but does not provide access to any diagnostic messages of
+those processes. As an end result, we can not offer these diagnostic 
+messages when, for example, reporting automated build failures to sites
+like C<testers.cpan.org>.
+
+=back
+
+=head1 AUTHOR
+
+Originally by Jos Boumans E<lt>kane@cpan.orgE<gt>.  Brought to working
+condition and currently maintained by Ken Williams E<lt>kwilliams@cpan.orgE<gt>.
+
+=head1 COPYRIGHT
+
+The CPAN++ interface (of which this module is a part of) is
+copyright (c) 2001, 2002, 2003, 2004, 2005 Jos Boumans E<lt>kane@cpan.orgE<gt>.
+All rights reserved.
+
+This library is free software;
+you may redistribute and/or modify it under the same
+terms as Perl itself.
+
+=cut
+
+1;
+
+# Local variables:
+# c-indentation-style: bsd
+# c-basic-offset: 4
+# indent-tabs-mode: nil
+# End:
+# vim: expandtab shiftwidth=4:
diff --git a/lib/CPANPLUS/Dist/Build/Constants.pm b/lib/CPANPLUS/Dist/Build/Constants.pm
new file mode 100644 (file)
index 0000000..1a089ff
--- /dev/null
@@ -0,0 +1,34 @@
+package CPANPLUS::Dist::Build::Constants;
+
+use strict;
+use File::Spec;
+
+BEGIN {
+
+    require Exporter;
+    use vars    qw[$VERSION @ISA @EXPORT];
+  
+    $VERSION    = 0.01;
+    @ISA        = qw[Exporter];
+    @EXPORT     = qw[ BUILD_DIR BUILD ];
+}
+
+
+use constant BUILD_DIR      => sub { return @_
+                                        ? File::Spec->catdir($_[0], '_build')
+                                        : '_build';
+                            }; 
+use constant BUILD          => sub { return @_
+                                        ? File::Spec->catfile($_[0], 'Build')
+                                        : 'Build';
+                            };
+                            
+1;
+
+
+# Local variables:
+# c-indentation-style: bsd
+# c-basic-offset: 4
+# indent-tabs-mode: nil
+# End:
+# vim: expandtab shiftwidth=4:
diff --git a/lib/CPANPLUS/Dist/Build/t/01_CPANPLUS-Dist-Build-Constants.t b/lib/CPANPLUS/Dist/Build/t/01_CPANPLUS-Dist-Build-Constants.t
new file mode 100644 (file)
index 0000000..c7cb348
--- /dev/null
@@ -0,0 +1,25 @@
+BEGIN { chdir 't' if -d 't' };
+
+### this is to make devel::cover happy ###
+BEGIN {
+    use File::Spec;
+    require lib;
+    for (qw[../lib inc]) {
+        my $l = 'lib'; $l->import(File::Spec->rel2abs($_)) 
+    }
+}
+
+use strict;
+use Test::More 'no_plan';
+
+my $Class = 'CPANPLUS::Dist::Build::Constants';
+
+
+use_ok( $Class );
+
+for my $name ( qw[BUILD BUILD_DIR] ) {
+
+    my $sub = $Class->can( $name );   
+    ok( $sub,                   "$Class can $name" );
+    ok( $sub->(),               "   $name called OK" );
+}    
diff --git a/lib/CPANPLUS/Dist/Build/t/02_CPANPLUS-Dist-Build.t b/lib/CPANPLUS/Dist/Build/t/02_CPANPLUS-Dist-Build.t
new file mode 100644 (file)
index 0000000..9417cec
--- /dev/null
@@ -0,0 +1,235 @@
+### make sure we can find our conf.pl file
+BEGIN { 
+    use FindBin; 
+    require "$FindBin::Bin/inc/conf.pl";
+}
+
+use strict;
+use CPANPLUS::Configure;
+use CPANPLUS::Backend;
+use CPANPLUS::Internals::Constants;
+use CPANPLUS::Module::Fake;
+use CPANPLUS::Module::Author::Fake;
+
+use Config;
+use Test::More      'no_plan';
+use File::Basename  ();
+use Data::Dumper;
+use Config;
+use IPC::Cmd        'can_run';
+
+$SIG{__WARN__} = sub {warn @_ unless @_ && $_[0] =~ /redefined|isn't numeric/};
+
+# Load these two modules in advance, even though they would be
+# auto-loaded, because we want to override some of their subs.
+use ExtUtils::Packlist;
+use ExtUtils::Installed;
+
+my $Class   = 'CPANPLUS::Dist::Build';
+my $Utils   = 'CPANPLUS::Internals::Utils';
+my $Have_CC =  can_run($Config{'cc'} )? 1 : 0;
+
+
+my $Lib     = File::Spec->rel2abs(File::Spec->catdir( qw[dummy-perl] ));
+my $Src     = File::Spec->rel2abs(File::Spec->catdir( qw[src] ));
+
+my $Verbose = @ARGV ? 1 : 0;
+my $CB      = CPANPLUS::Backend->new;
+my $Conf    = $CB->configure_object;
+
+
+### create a fake object, so we don't use the actual module tree
+my $Mod = CPANPLUS::Module::Fake->new(
+                module  => 'Foo::Bar',
+                path    => 'src',
+                author  => CPANPLUS::Module::Author::Fake->new,
+                package => 'Foo-Bar-0.01.tar.gz',
+            );
+
+$Conf->set_conf( base       => 'dummy-cpanplus' );
+$Conf->set_conf( dist_type  => '' );
+$Conf->set_conf( verbose    => $Verbose );
+$Conf->set_conf( signature  => 0 );
+### running tests will mess with the test output so skip 'm
+$Conf->set_conf( skiptest   => 1 );
+
+                # path, cc needed?
+my %Map     = ( noxs    => 0,
+                xs      => 1 
+            );        
+
+
+### Disable certain possible settings, so we dont accidentally
+### touch anything outside our sandbox
+{   
+    ### set buildflags to install in our dummy perl dir
+    $Conf->set_conf( buildflags => "install_base=$Lib" );
+    
+    ### don't start sending test reports now... ###
+    $CB->_callbacks->send_test_report( sub { 0 } );
+    $Conf->set_conf( cpantest => 0 );
+    
+    ### we dont need sudo -- we're installing in our own sandbox now
+    $Conf->set_program( sudo => undef );
+}
+
+use_ok( $Class );
+
+ok( $Class->format_available,   "Format is available" );
+
+
+while( my($path,$need_cc) = each %Map ) {
+
+    my $mod = $Mod->clone;
+    ok( $mod,                   "Module object created for '$path'" );        
+                
+    ### set the fetch location -- it's local
+    {   my $where = File::Spec->rel2abs(
+                            File::Spec->catdir( $Src, $path, $mod->package )
+                        );
+                        
+        $mod->status->fetch( $where );
+
+        ok( -e $where,          "   Tarball '$where' exists" );
+    }
+
+    ok( $mod->prepare,          "   Preparing module" );
+
+    ok( $mod->status->dist_cpan,    
+                                "   Dist registered as status" );
+
+    isa_ok( $mod->status->dist_cpan, $Class );
+
+    ok( $mod->status->dist_cpan->status->prepared,
+                                "   Prepared status registered" );
+    is( $mod->status->dist_cpan->status->distdir, $mod->status->extract,
+                                "   Distdir status registered properly" );
+
+
+    is( $mod->status->installer_type, INSTALLER_BUILD, 
+                                "   Proper installer type found" );
+
+
+    ### we might not have a C compiler
+    SKIP: {
+        skip("The CC compiler listed in Config.pm is not available " .
+             "-- skipping compile tests", 5) if $need_cc && !$Have_CC;
+        skip("Module::Build is not compiled with C support ".
+             "-- skipping compile tests", 5) 
+             unless Module::Build->_mb_feature('C_support');
+
+        ok( $mod->create( ),    "Creating module" );
+        ok( $mod->status->dist_cpan->status->created,
+                                "   Created status registered" );
+
+        ### install tests
+        SKIP: {
+            skip("Install tests require Module::Build 0.2606 or higher", 2)
+                unless $Module::Build::VERSION >= '0.2606';
+        
+            ### flush the lib cache
+            ### otherwise, cpanplus thinks the module's already installed
+            ### since the blib is already in @INC
+            $CB->_flush( list => [qw|lib|] );
+        
+            ### force the install, make sure the Dist::Build->install()
+            ### sub gets called
+            ok( $mod->install( force => 1 ),
+                                "Installing module" ); 
+            ok( $mod->status->installed,    
+                                "   Status says module installed" );
+        }
+
+        SKIP: {
+            my $minversion = 0.2609;
+            skip(qq[Uninstalling requires at least Module::Build $minversion], 1)
+              unless eval { Module::Build->VERSION($minversion); 1 };
+
+            # The installation directory actually needs to be in @INC
+            # in order to test uninstallation
+            'lib'->import( File::Spec->catdir($Lib, 'lib', 'perl5') );
+
+            # EU::Installed and CP+::M are only capable of searching
+            # for modules in the core directories.  We need to fake
+            # them out with our own subs here.
+            my $packlist = find_module($mod->name . '::.packlist');
+            ok $packlist, "Found packlist";
+            
+            my $p = ExtUtils::Packlist->new($packlist);
+            ok keys(%$p) > 0, "Packlist contains entries";
+
+            local *CPANPLUS::Module::installed_version = sub {1};
+            local *CPANPLUS::Module::packlist = sub { [$p] };
+            local *ExtUtils::Installed::files = sub { keys %$p };
+            
+            ok( $mod->uninstall,"Uninstalling module" );
+        }
+    }
+
+    ### throw away all the extracted stuff
+    $Utils->_rmdir( dir => $Conf->get_conf('base') );
+}
+
+### test ENV setting while running Build.PL code
+{   ### use print() not die() -- we're redirecting STDERR in tests!
+    my $env     = 'ENV_CPANPLUS_IS_EXECUTING';
+    my $clone   = $Mod->clone;
+    
+    ok( $clone,                 'Testing ENV settings $dist->prepare' );
+    
+    $clone->status->fetch( File::Spec->catfile($Src, 'noxs', $clone->package) );
+    ok( $clone->extract,        '   Files extracted' );
+    
+    ### write our own Build.PL file    
+    my $build_pl = BUILD_PL->( $clone->status->extract );
+    {   my $fh   = OPEN_FILE->( $build_pl, '>' );
+        print $fh "die qq[ENV=\$ENV{$env}\n];";
+        close $fh;
+    }
+    ok( -e $build_pl,           "   File exists" );
+
+    ### clear errors    
+    CPANPLUS::Error->flush;
+
+    ### since we're die'ing in the Build.PL, do a local *STDERR,
+    ### so we dont spam the result through the test -- this is expected
+    ### behaviour after all.
+    my $rv = do { local *STDERR; $clone->prepare( force => 1 ) };
+    ok( !$rv,                   '   $mod->prepare failed' );
+
+    my $re = quotemeta( $build_pl );
+    like( CPANPLUS::Error->stack_as_string, qr/ENV=$re/,
+                                "   \$ENV $env set correctly during execution");
+
+    ### and the ENV var should no longer be set now
+    ok( !$ENV{$env},            "   ENV var now unset" );
+}    
+
+
+sub find_module {
+  my $module = shift;
+
+  # Don't add the .pm yet, in case it's a packlist or something like ExtUtils::xsubpp.
+  my $file = File::Spec->catfile( split m/::/, $module );
+  my $candidate;
+  foreach (@INC) {
+    if (-e ($candidate = File::Spec->catdir($_, $file))
+        or
+        -e ($candidate = File::Spec->catdir($_, "$file.pm"))
+        or
+        -e ($candidate = File::Spec->catdir($_, 'auto', $file))
+        or
+        -e ($candidate = File::Spec->catdir($_, 'auto', "$file.pm"))) {
+      return $candidate;
+    }
+  }
+  return;
+}
+
+
+# Local variables:
+# c-indentation-style: bsd
+# c-basic-offset: 4
+# indent-tabs-mode: nil
+# End:
+# vim: expandtab shiftwidth=4:
diff --git a/lib/CPANPLUS/Dist/Build/t/inc/conf.pl b/lib/CPANPLUS/Dist/Build/t/inc/conf.pl
new file mode 100644 (file)
index 0000000..1892922
--- /dev/null
@@ -0,0 +1,201 @@
+### XXX copied from cpanplus's t/inc/conf.pl
+BEGIN {
+    use FindBin; 
+    use File::Spec;
+    
+    ### paths to our own 'lib' and 'inc' dirs
+    ### include them, relative from t/
+    my @paths   = map { "$FindBin::Bin/$_" } qw[../lib inc];
+
+    ### absolute'ify the paths in @INC;
+    my @rel2abs = map { File::Spec->rel2abs( $_ ) }
+                    grep { not File::Spec->file_name_is_absolute( $_ ) } @INC;
+    
+    ### use require to make devel::cover happy
+    require lib;
+    for ( @paths, @rel2abs ) { 
+        my $l = 'lib'; 
+        $l->import( $_ ) 
+    }
+
+    use Config;
+
+    ### and add them to the environment, so shellouts get them
+    $ENV{'PERL5LIB'} = join ':', 
+                        grep { defined } $ENV{'PERL5LIB'}, @paths, @rel2abs;
+    
+    ### add our own path to the front of $ENV{PATH}, so that cpanp-run-perl
+    ### and friends get picked up
+    $ENV{'PATH'} = join $Config{'path_sep'}, 
+                    grep { defined } "$FindBin::Bin/../../../bin", $ENV{'PATH'};
+
+    ### Fix up the path to perl, as we're about to chdir
+    ### but only under perlcore, or if the path contains delimiters,
+    ### meaning it's relative, but not looked up in your $PATH
+    $^X = File::Spec->rel2abs( $^X ) 
+        if $ENV{PERL_CORE} or ( $^X =~ m|[/\\]| );
+
+    ### chdir to our own test dir, so we know all files are relative 
+    ### to this point, no matter whether run from perlcore tests or
+    ### regular CPAN installs
+    chdir "$FindBin::Bin" if -d "$FindBin::Bin"
+}
+
+BEGIN {
+    use IPC::Cmd;
+   
+    ### Win32 has issues with redirecting FD's properly in IPC::Run:
+    ### Can't redirect fd #4 on Win32 at IPC/Run.pm line 2801
+    $IPC::Cmd::USE_IPC_RUN = 0 if $^O eq 'MSWin32';
+    $IPC::Cmd::USE_IPC_RUN = 0 if $^O eq 'MSWin32';
+}
+
+use strict;
+use CPANPLUS::Configure;
+
+use File::Path      qw[rmtree];
+use FileHandle;
+use File::Basename  qw[basename];
+
+{   ### Force the ignoring of .po files for L::M::S
+    $INC{'Locale::Maketext::Lexicon.pm'} = __FILE__;
+    $Locale::Maketext::Lexicon::VERSION = 0;
+}
+
+1;
+
+__END__
+
+# prereq has to be in our package file && core!
+use constant TEST_CONF_PREREQ           => 'Cwd';   
+use constant TEST_CONF_MODULE           => 'Foo::Bar::EU::NOXS';
+use constant TEST_CONF_INST_MODULE      => 'Foo::Bar';
+use constant TEST_CONF_INVALID_MODULE   => 'fnurk';
+use constant TEST_CONF_MIRROR_DIR       => 'dummy-localmirror';
+
+### we might need this Some Day when we're installing into
+### our own sandbox. see t/20.t for details
+# use constant TEST_INSTALL_DIR       => do {
+#     my $dir = File::Spec->rel2abs( 'dummy-perl' );
+# 
+#     ### clean up paths if we are on win32    
+#     ### dirs with spaces will be.. bad :(
+#     $^O eq 'MSWin32'
+#         ? Win32::GetShortPathName( $dir )
+#         : $dir;
+# };        
+
+# use constant TEST_INSTALL_DIR_LIB 
+#     => File::Spec->catdir( TEST_INSTALL_DIR, 'lib' );
+# use constant TEST_INSTALL_DIR_BIN 
+#     => File::Spec->catdir( TEST_INSTALL_DIR, 'bin' );
+# use constant TEST_INSTALL_DIR_MAN1 
+#     => File::Spec->catdir( TEST_INSTALL_DIR, 'man', 'man1' );
+# use constant TEST_INSTALL_DIR_MAN3
+#     => File::Spec->catdir( TEST_INSTALL_DIR, 'man', 'man3' );
+# use constant TEST_INSTALL_DIR_ARCH
+#     => File::Spec->catdir( TEST_INSTALL_DIR, 'arch' );
+# 
+# use constant TEST_INSTALL_EU_MM_FLAGS =>
+#     ' INSTALLDIRS=site' .
+#     ' INSTALLSITELIB='     . TEST_INSTALL_DIR_LIB .
+#     ' INSTALLSITEARCH='    . TEST_INSTALL_DIR_ARCH .    # .packlist
+#     ' INSTALLARCHLIB='     . TEST_INSTALL_DIR_ARCH .    # perllocal.pod
+#     ' INSTALLSITEBIN='     . TEST_INSTALL_DIR_BIN .
+#     ' INSTALLSCRIPT='      . TEST_INSTALL_DIR_BIN .
+#     ' INSTALLSITEMAN1DIR=' . TEST_INSTALL_DIR_MAN1 .
+#     ' INSTALLSITEMAN3DIR=' . TEST_INSTALL_DIR_MAN3;
+
+
+sub gimme_conf { 
+    my $conf = CPANPLUS::Configure->new();
+    $conf->set_conf( hosts  => [ { 
+                        path        => 'dummy-CPAN',
+                        scheme      => 'file',
+                    } ],      
+    );
+    $conf->set_conf( base       => 'dummy-cpanplus' );
+    $conf->set_conf( dist_type  => '' );
+    $conf->set_conf( signature  => 0 );
+
+    _clean_test_dir( [
+        $conf->get_conf('base'),     
+        TEST_CONF_MIRROR_DIR,
+#         TEST_INSTALL_DIR_LIB,
+#         TEST_INSTALL_DIR_BIN,
+#         TEST_INSTALL_DIR_MAN1, 
+#         TEST_INSTALL_DIR_MAN3,
+    ], 1 );
+        
+    return $conf;
+};
+
+{
+    my $fh;
+    my $file = ".".basename($0).".output";
+    sub output_handle {
+        return $fh if $fh;
+        
+        $fh = FileHandle->new(">$file")
+                    or warn "Could not open output file '$file': $!";
+       
+        $fh->autoflush(1);
+        return $fh;
+    }
+    
+    sub output_file { return $file }
+}
+
+
+### clean these files if we're under perl core
+END { 
+    if ( $ENV{PERL_CORE} ) {
+        close output_handle(); 1 while unlink output_file();
+
+        _clean_test_dir( [
+            gimme_conf->get_conf('base'),   
+            TEST_CONF_MIRROR_DIR,
+    #         TEST_INSTALL_DIR_LIB,
+    #         TEST_INSTALL_DIR_BIN,
+    #         TEST_INSTALL_DIR_MAN1, 
+    #         TEST_INSTALL_DIR_MAN3,
+        ], 1 );
+    }
+}
+
+
+
+### whenever we start a new script, we want to clean out our
+### old files from the test '.cpanplus' dir..
+sub _clean_test_dir {
+    my $dirs    = shift || [];
+    my $verbose = shift || 0;
+
+    for my $dir ( @$dirs ) {
+
+        my $dh;
+        opendir $dh, $dir or die "Could not open basedir '$dir': $!";
+        while( my $file = readdir $dh ) { 
+            next if $file =~ /^\./;  # skip dot files
+            
+            my $path = File::Spec->catfile( $dir, $file );
+            
+            ### directory, rmtree it
+            if( -d $path ) {
+                print "Deleting directory '$path'\n" if $verbose;
+                eval { rmtree( $path ) };
+                warn "Could not delete '$path' while cleaning up '$dir'" if $@;
+           
+            ### regular file
+            } else {
+                print "Deleting file '$path'\n" if $verbose;
+                1 while unlink $path;
+            }            
+        }       
+    
+        close $dh;
+    }
+    
+    return 1;
+}
+1;
diff --git a/lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed b/lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed
new file mode 100644 (file)
index 0000000..e978261
--- /dev/null
@@ -0,0 +1,35 @@
+#########################################################################
+This is a binary file that was packed with the 'uupacktool.pl' which
+is included in the Perl distribution.
+
+To unpack this file use the following command:
+
+     uupacktool.pl -u lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz
+
+To recreate it use the following command:
+
+     uupacktool.pl -p lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed
+
+Created at Fri May  4 14:00:53 2007
+#########################################################################
+__UU__
+M'XL("-<X34(``T9O;RU"87(M,"XP,2YT87(`[9E;;]HP%,=Y]J<X+9722@-R
+M!0G6:G2#M=)*I]+MI:M0((9&)#;*I5TU[;O/)J1JH!WK%-)U/;\7XTOXVSD^
+M]G'<Y;QR:`<5M:IJM=)F4`4-RY*IUK"T^VE*25-UW=`-T]`;)573+<,J@;6A
+M_F2(P\@.`$I3F]'?M5M7GPXD35\(W?OV?W_4[GWL]//6D.^C;IJ/VU\WE^QO
+M6*(("GF)K]S^QV#[8,/"\C!V/4J>NT](<63\_Z3=.^YV^N<Y:ZSS?TVM+_M_
+MO6Z@_Q?!PO')6:?]X:1##F/7<ZJ?/Y%T*I"(AE%UYA'/'=;$9*F)R5*=^63H
+MLMJ8\Z',>;ABO%@R_K^P==X::_?_NK;D_Z9JUM'_BZ"\58O#H";=>48#CY!9
+MX+((MK5J5?O&MEMIGD\AR3]WAY%<R?B_7.,WH/'T\Y]AJ`:>_XI@Q?YRC\]9
+MX^GV-U6]CO8O@@?MG\1XN6FLV?^EZ9?L;ZD8_Q<#V;^BMJ-!KRVB?]*W_9E'
+MH0)B%H`([R%,"L1/DK9L?SD_.CV[:WMB,WB;-'M'O\_3ZHC[!W?M3\^/.J+Y
+M-0U"ES/P7$9#<!G,N`/A%8\]!X84W`GC`76:9.=KYZQ_?-J#?5`8CX!/FS`1
+M:?K\.."^?'9+:0F)41R)D,4>3>T)E9UN-L7<%15E6-$3G?(IB\*'1,OP1[*+
+MOYA+ER&Z<L4?AR*E&36XH4I`P>-\ZK*)>'=!9E2[BO0U9>]"O6P!E.]UAU[;
+MGN(0\H/(\AN:5G'FW4)D3^E<:NP&8905'/.8B5&`T!S9GDCAT?&$=,29LSPL
+MA\HQ_21$PP#O=9%9_^4I8`,:?Q7_X??_0EBQ_]U'G?PTUI[_36MY_]<T#??_
+M(GCL_)],`W'>QRWAOR;C_^G7WYPUUOF_;C16SG\&QO^%$(<BA.=.[%$1.DOS
+MMX`0_Q9VAC(CHD=&;[(-1'BY*T-,?UXX8+9/8?\`E#3Z5M[(VG`4N+-H(.\3
+M0UE]`4IF=U'@4K;;:Y%$J7(P"J@=T<$\-T@>QY5GXV3\/[D$REUC[?ZO&\OQ
+MGZFIZ/]%L+C_3RR/U_\(@B`(@B`(@B`(@B`(@B`(@B`(@B`(@B`(@B`(@B`(
+,\D_S"QCQWFL`4```
diff --git a/lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed b/lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed
new file mode 100644 (file)
index 0000000..52e09e2
--- /dev/null
@@ -0,0 +1,50 @@
+#########################################################################
+This is a binary file that was packed with the 'uupacktool.pl' which
+is included in the Perl distribution.
+
+To unpack this file use the following command:
+
+     uupacktool.pl -u lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz
+
+To recreate it use the following command:
+
+     uupacktool.pl -p lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed
+
+Created at Fri May  4 14:00:54 2007
+#########################################################################
+__UU__
+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
++R_`55?+KB0!0````