{
    ## A clean lexical environment with no strictures in main::
    sub SteamRoller::_eval{
        my $dest = pop;
        warn ">>>evalling $dest<<<\n" if $ENV{DEBUGSTEAMROLLEDSCRIPT};
        eval shift( @_ ) . "; 1;" or die $@;
        $INC{$dest} = "evalled" if length $dest;
    };
}
{
package SteamRoller;
use strict;
use Cwd;
use File::Spec;
use File::Basename qw( fileparse );

## The BEGINs are so the monster strings below get parsed, used, and
## deallocated one at a time.  Not using __END__/__DATA__ to allow
## adding user code that does so.

my $original_cwd;
my $parent_dir;
my $dont_spew;
my $digest;
my $id_fn;
my @lib_dirs;
my $command_path;

BEGIN {
$digest = "RoG4+vNbQLmxc7k9lgdJAQ Fyv76GQM/IJZm/hDKT1ptw sgiiDnByqYMxv4vHpRgAFw lSCUynooPDHX4f9/aeMalw xDKzBD6KKedNedqqWpq5kA qaT5pw+wdYdbqZRqrgcyjg aB2ANA2XQaF1J6Rr1QtpPw OZPbbNw+0BZE/bp1cBCBzQ YAijTC+3BNlKvPYXkQ1ToQ TaRUXbQyUJDujTfk8geBcw zwcNVfbqHqKueZtEvFSlyw WlxIsIj8uEHodFSTs6hUWg toTh5TIuuOS4AapRhndtGw HNlpWdUSZSwUkOXLVHAjgg gWh0Foi8L6QFKgH6hJezsg qDR9CTJfeFWgcP70gee/sQ Bmda8HJB/D3DuXifJ+XKyw";
$id_fn = "id_JKXmyOtBsAjcxIWsyYNBfg";
@lib_dirs = ( 'lib' );
$command_path = "";

$original_cwd = cwd;

sub _spew {
    return if $dont_spew;
    my $dest = pop;
    warn ">>>spewing $dest<<<\n" if $ENV{DEBUGSTEAMROLLEDSCRIPT};
    $dest = File::Spec->catfile( $parent_dir, $dest );
    my $dest_parent = (fileparse( $dest ))[1];
    ## 0775: Trust in the user's umask "a bit", but don't allow writing
    mkpath( [ $dest_parent ], 0, 0775 ) unless -d $dest_parent;
    sysopen F, $dest, O_CREAT()|O_WRONLY(), 0775 or die "$!: $dest\n";
    push @_, pop;  ## make read/writable
    chomp $_[-1];
    print F @_                                   or die "$!: $dest\n";
    close F                                      or die "$!: $dest\n";
}


## Search for an existing copy.
## This is more to prevent overwriting an old (put possibly still
## in service) copy than to speed things up.  spewing a bunch
## of files like we do is pretty quick.
my $payload_number = 0;
while () {
    $parent_dir = File::Spec->catdir(
        File::Spec->tmpdir,
        scalar fileparse( $0 ) . "-tmp-$payload_number"
    );

    last unless -d $parent_dir;

    my $id_file = File::Spec->catdir( $parent_dir, $id_fn );

    if ( -f $id_file ) {
        my $id = do $id_file;
        if ( $id eq $digest ) {
            $dont_spew = 1;
            last;
        }
    }

    ++$payload_number;
}

eval <<LAZY_LOADS unless $dont_spew;
    use Fcntl qw( O_CREAT O_WRONLY );
    use File::Path qw( mkpath );
LAZY_LOADS

_spew qq{"$digest"\n}, $id_fn;

if ( $ENV{STEAMROLLEDSCRIPTIGNORESITELIB} ) {
     use Config;
     @INC = grep index( $_, $Config{sitelib_stem} ), @INC;
}

unshift @INC, 
    map(
        File::Spec->catdir( $parent_dir, $_ ),
        @lib_dirs
    );
}
#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Package::PerlModule"
package MBall::Package::PerlModule;

@ISA = qw( MBall::Package );

use strict;

sub _cmp_ver {
    my $self = shift;

    my ( $vera, $verb ) = @_;

    $vera =~ s/_//g;
    $verb =~ s/_//g;

    return ($vera || -1) <=> ($verb || -1 );
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Package/PerlModule.pm"} = "inlined" if length "MBall/Package/PerlModule.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::DB"
package MBall::DB;

## This is a separate package to (eventually) allow others to use their
## own defaults.  It mereley returns the data structure for now.

use Cwd;

sub default_db {
return {
#    Packages => [  ## loaded from a "packages" file, usually.
#        {
#            Name        => "VCP",
#            Style       => "PerlModule",
#        },
#
#        {
#            Name        => "Pod::Links",
#            Style       => "PerlModule",
#            Actions => [
#                {
#                    Phase  => "unpack",
#                    Action => "RunCmd",
#                    CmdLine =>
#$^X . q( -i.bak -pe 'BEGIN { for ( @ARGV ) { die "$!: $_\n" unless -f $_ }; } print "require IO::File;\n" if $. == 1' Pod/HTML_Elements.pm),
#                    Comment => "Load IO::File",
#
#                },
#            ],
#        },
#    ],

    ## TODO: Move some of these in to Classes => { Package => { ... } }.

    ## Defaults for macros; these are often overridden in the various
    ## package "styles".
    WorkRoot      => cwd,

    ## Where to do all of our work in
    WorkDir       => "<%WorkRoot%>",

    ## Where to untar and build the distros
    BuildRoot     => "<%WorkDir%>/build",
    BuildDir      => "<%BuildRoot%>/<%DistBaseName%>",

    ## Where to build re-distributions in
    RedistBuildDir => "<%WorkDir%>/redist/<%DistBaseName%>",

    ## Where to unpack a distro before moving it to <%BuildDir%>
    ## This is needed for two reasons: some distros unzip in to
    ## the cwd instead of a subdir, and others create a subdir
    ## that is not eponymous with the distro file name
    UnpackTmpDir  => "<%BuildRoot%>/unpack",

    ## Where to keep the distros
    DistsDir      => "<%WorkDir%>/dists",

#    OriginalsDir  => "<%WorkDir%>/originals", # not used yet

    ## For files that may be needed for several runs, but should not
    ## be tarred up.
    MetaInfoDir   => "<%BuildRoot%>/meta",
    DistName      => "<%Name%>",
    DistBaseName  => "<%DistName%>-<%Version%>",
    DistFileNames => "<%DistBaseName%>.tar.gz <%DistBaseName%>.tgz",
    DistFiles     => "<%DistsDir%>/<%DistFileNames%>",

    Perms         => 0755,  ## default perms for created files

    Path          => sub { $ENV{PATH} },

    BuildEnv      => {
        LD_LIBRARY_PATH => "<%Prefix%>/lib",
        PATH            => "<%Prefix%>/bin:<%Path%>",
        PERL5LIB        => "<%Prefix%>/lib",
    },

    Prefix        => "<%WorkRoot%>/www",

    Classes => {

        Package => {
            ConfigCmd        => "./configure <%ConfigParms%>",
            ConfigParms      => "--prefix=<%Prefix%>",
            DetectedRequires => [],
        },

        Package::RawDirectory => {
            ## This only works because make doesn't ever happen, so
            ## the install ends up reisntalling everything.
            ## TODO: Get this package style implemented properly.
            DistDir => "<%DistsDir%>/<%DistBaseName%>",
            Actions => [
                {
                    Phase  => "install",
                    Action => "CopyFiles",
                    Paths  => "<%DistDir%>",
                    Dest   => "<%Prefix%>",
                },
            ],
        },

        ## TODO: Split in to "PerlModule" and "CPANModule"
        Package::PerlModule => {
            ConfigCmd   => "$^X Makefile.PL <%ConfigParms%> < /dev/null",

            TestCmd     => "make test",

            VersionFrom => "<%Name%>",

            ConfigParms => {
                PREFIX          => "<%Prefix%>",
                INSTALLSITELIB  => "<%Prefix%>/lib",
                INSTALLSITEARCH => "<%Prefix%>/lib",
            },

            FinderClasses => "CPAN",

            Version => sub {
                ## Use 02packages.details.txt to fill in missing version
                ## numbers
                my $self = shift;
                return $self->_finder( "CPAN" )
                    ->query_latest_version( $self->VersionFrom );
            },

            BuildPrereqPackage => sub {
                my $self = shift;
                my ( $name, $options ) = @_;

                return {
                    Name          => $options->{Name},
                    Style         => "PerlModule",
                    AutoGenerated => 1,
                };
            },

            DetectedRequires => sub {
                my $self = shift;

                $self->_log( "detecting prerequisites (by configuring)" )
                    if $self->_tracing;

                my $fn = File::Spec->catfile(
                    $self->BuildDir,
                    "Makefile"
                );

                my $i_configured_it;

                unless ( -f $fn ) {
                    $self->_log( "$fn not found, building" )
                        if $self->_tracing;

                    ## Make sure the unpack happens, since it was likely
                    ## already probed, and thus will avoid doing any
                    ## additional work unless this cache is cleared.
                    ## TODO: use a "real" queuing method for this.
                    local $self->{_Container}->{_cached_checks} = {};

                    $self->reconfigure;  ## configure again even if configured
                    $i_configured_it = 1;
                }

                $self->cd_to( "BuildDir" );
                my %prereqs;
                open F, "<$fn" or $self->_os->_fail_errno( "opening Makefile" );
                ## ummm, "inspired by" CPAN.pm with minor touchups
                while (<F>) {
                    last if /MakeMaker post_initialize section/;
                    my ( $prereqs ) = m/^#\s+PREREQ_PM\s+=>\s+(.+)/;
                    if ( $prereqs ) {
                        while ( $prereqs =~ m/(?:\s)([\w\:]+)=>q\[(.*?)\],?/g ){
                            # In case a prereq is mentioned twice, complain.
                            $prereqs{$1} = $2;
                        }
                        last;
                    }
                }
                close F;

use Data::Dumper; warn Dumper( \%prereqs );

                ## Ignore any that ship with Perl.
                my $finder = $self->_finder( "CPAN" );
                my @module_names = (
                    grep {
                        my $name = eval {
                            $finder->query_dist_name( $_ );
                        };
                        ## If not found or it's not in a perl.... dist
                        ! defined $name || 0 != index $name, "perl";
                    } 
                    keys %prereqs
                );

                for ( @module_names ) {
                    if (
                        my $pkg = eval {
                            $self->{_Container}->get_package( $_ );
                        }
                    ) {
                        if ( ! defined $pkg->{MinVersion}
                            || $self->_cmp_ver(
                                $prereqs{$_},
                                $pkg->{MinVersion}
                            ) > 1
                        ) {
                            $pkg->{MinVersion} = $prereqs{$_};
                        }
                        next;
                    }

                    $self->{_Container}->add_package(
                        {
                            Name          => $_,
                            Style         => "PerlModule",
                            MinVersion    => $prereqs{$_},
                            AutoGenerated => 1,
                        }
                    );
                }

                ## Make sure we reconfigure if there are modules needed.
                ## NOTE: it would be nice to flag this somewhere and only
                ## clean it up in the event of (any) missing prereqs.
                $self->rm_sentinal_file( "configure" )
                    if $i_configured_it && @module_names;

                return \@module_names;
            },

            InstalledVersionQuery =>
                $^X . q[ -le ']
                .q[BEGIN {]
                .q[ use Config;]
                .q[ @INC = grep index( $_, defined $Config{sitelib_stem} ? $Config{sitelib_stem} : $Config{sitelibexp} ), @INC;]
                .q[}]
                .q[use <%VersionFrom%> (); $_ = $<%VersionFrom%>::VERSION; s/_//; print $_'],
            DistName => sub {
                my $self = shift;
                return $self->_finder( "CPAN" )->query_dist_name( $self->Name );
            },

            DistBaseName => sub {
                my $self = shift;
                $self->_finder( "CPAN" )->query_dist_base_name( $self->Name );
            },

            DistFileNames => sub {
                my $self = shift;
                return
                 $self->_finder( "CPAN" )->query_dist_file_names( $self->Name );
            },

            CPAN_URIs => [qw(
                http://www.cpan.org/modules/by-authors/id
                ftp://ftp.cs.colorado.edu/pub/perl/CPAN/modules/by-authors/id
                ftp://cpan.nas.nasa.gov/pub/perl/CPAN/modules/by-authors/id
            )],

            Actions => [
                {
                    Phase   => "unpack",
                    Action  => "RunCmd",
                    CmdLine =>
$^X . q( -0777 -i.bak -pe 's/^(?!#)/BEGIN { use Config; \@INC = grep index( \$_, defined \$Config{sitelib_stem}? \$Config{sitelib_stem} : \$Config{sitelibexp}  ), \@INC }\n/m' Makefile.PL),
                    Comment =>
                        "Preventing perl Makefile.PL from searching in site_perl or site/lib",
                },
                ## WARNING: This trick won't work in packages.mball, since
                ## it's parsed to perl, then translated back to source.
                ## TODO: implement layered data structures.
                $] >= 5.006000
                    ? ()
                    : {
                        Phase   => "configure",
                        Action  => "RunCmd",
                        CmdLine =>
    $^X . q( -0777 -i.bak -pe 's/^((FULL)?PERL\s*=\s*).*/${1}) . $^X . q(/mg' Makefile),
                        Comment =>
                            "Fix 'PERL = ' in perl5.00503 (at least) generated Makefiles",
                    }
            ],
        },

        Package::GNOME => {
            InstalledVersionQuery => q{<%Prefix%>/bin/<%Name%>-config --version},
            GNOME_URIs => [qw(
                ftp://ftp.yggdrasil.com/mirrors/site/ftp.gnome.org/pub/GNOME/stable/sources
                ftp://ftp.gnome.org/pub/GNOME/stable/sources
            )],
            URIs => "<%GNOME_URIs%>/<%DistName%>/<%DistFileNames%>",
        },

        Finder::CPAN => {

            CPAN_URIs => [qw(
                http://www.cpan.org
                http://www.perl.com/CPAN
                ftp://ftp.cs.colorado.edu/pub/perl/CPAN
                ftp://cpan.nas.nasa.gov/pub/perl/CPAN
            )],

            PackagesDetailsFileName => "02packages.details.txt",

            LocalPackagesDetailsFileName =>
                "<%MetaInfoDir%>/<%PackagesDetailsFileName%>",

            ## PackagesDetailsDistFileNames is concocted at run time
            ## based on what compression tools, if any, are lying around.
            PackagesDetailsURIs =>
                "<%CPAN_URIs%>/modules/<%PackagesDetailsDistFileNames%>",

            DistFilePaths => sub {
                my $self = shift;

                return $self->_guess_relevant_record(
                    $self->Name
                )->{Path};
            },

            URIs => "<%CPAN_URIs%>/modules/by-authors/id/<%DistFilePaths%>",
        }
    },
};
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/DB.pm"} = "inlined" if length "MBall/DB.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Action::WriteFile"
package MBall::Action::WriteFile;

@ISA = qw( MBall::Action );

use strict;

sub validate {
    my $self = shift;

    my @errors;

    eval { $self->SUPER::validate } or push @errors, $@;

    push @errors, $self->_fail_msg( "missing Path")
        unless exists $self->{Path};

    push @errors, $self->_fail_msg( "missing Body")
        unless exists $self->{Body};

    die @errors if @errors;

    1;
}


sub execute {
    my $self = shift;

    $self->_os->write_file( $self->Path, $self->Perms, $self->Body );
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Action/WriteFile.pm"} = "inlined" if length "MBall/Action/WriteFile.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Action::RunCmd"
package MBall::Action::RunCmd;

@ISA = qw( MBall::Action );

use strict;

sub validate {
    my $self = shift;

    my @errors;

    eval { $self->SUPER::validate } or push @errors, $@;

    push @errors, $self->_fail_msg( "missing Cmd")
        unless exists $self->{CmdLine};

    die @errors if @errors;

    1;
}


sub execute {
    my $self = shift;

    my $pkg = $self->{_Container};

    $self->_confess( "Action not in Package" )
        unless $pkg->isa( "MBall::Package" );

    $pkg->_run_build_cmd( $self->CmdLine );
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Action/RunCmd.pm"} = "inlined" if length "MBall/Action/RunCmd.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Action::DeleteFile"
package MBall::Action::DeleteFile;

@ISA = qw( MBall::Action );

use strict;

sub validate {
    my $self = shift;

    my @errors;

    eval { $self->SUPER::validate } or push @errors, $@;

    push @errors, $self->_fail_msg( "missing Path")
        unless exists $self->{Path};

    die @errors if @errors;

    1;
}


sub execute {
    my $self = shift;

    my $c = $self->Comment;
    my $p = $self->Path;
    return if $self->IfExists && ! -e $p;

    $self->_log( $c ) if defined $c && length $c;
    $self->_os->rm( $p );
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Action/DeleteFile.pm"} = "inlined" if length "MBall/Action/DeleteFile.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Action::DeleteDir"
package MBall::Action::DeleteDir;

@ISA = qw( MBall::Action );

use strict;

sub validate {
    my $self = shift;

    my @errors;

    eval { $self->SUPER::validate } or push @errors, $@;

    push @errors, $self->_fail_msg( "missing Path")
        unless exists $self->{Path};

    die @errors if @errors;

    1;
}


sub execute {
    my $self = shift;
    $self->_os->rmtree( $self->Path );
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Action/DeleteDir.pm"} = "inlined" if length "MBall/Action/DeleteDir.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Action::CopyFiles"
package MBall::Action::CopyFiles;

@ISA = qw( MBall::Action );

use strict;

use File::Spec;

sub validate {
    my $self = shift;

    my @errors;

    eval { $self->SUPER::validate } or push @errors, $@;

    push @errors, $self->_fail_msg( "missing Paths" )
        unless exists $self->{Paths};

    push @errors, $self->_fail_msg( "missing Dest")
        unless exists $self->{Dest};

    my $dest = $self->Dest;

    push @errors, $self->_fail_msg( "Dest not absolute: $dest" )
        unless File::Spec->file_name_is_absolute( $dest );

    die @errors if @errors;

    1;
}


sub execute {
    my $self = shift;

    require File::Basename;

    my $paths = $self->Paths( { Flatten => 0 } );
    $paths = [ $paths ] unless ref $paths;
    my $dest = $self->Dest;

    my @errors;

    if ( -e $dest ) {
        push @errors, $self->_fail_msg( "Dest not a directory: $dest" )
            unless -d $dest;
    }
    else {
        $self->_os->mkpath( $dest );
    }

    my $dirs = 0;
    my $files = 0;

    my @ok;
    for ( @$paths ) {
        if ( ! -e ) {
            push @errors, $self->_fail_msg( "Path does not exist: $_\n" );
            next;
        }
        push @ok, $_;
    }

    die @errors if @errors;
    ## TODO: offer an option to do as much as possible if an error is found
    for ( @ok ) {
        if ( -f ) {
            $self->_os->copy_file(
                $_, 
                File::Spec->catfile(
                    $dest,
                    File::Basename::basename( $_ ),
                )
            );
        }
        else {
            require File::Find;
            my $dir = $_;
            File::Find::find(
                sub {
                    return unless -f $_;
                    my $rel_path = File::Spec->abs2rel(
                        $File::Find::name,
                        $dir,
                    );
                    my $dest_path = 
                        File::Spec->catfile( 
                            $dest,
                            $rel_path,
                        );
                    $self->_os->copy_file(
                        $File::Find::name,
                        $dest_path
                    );
                },
                $dir
            );
        }
    }
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Action/CopyFiles.pm"} = "inlined" if length "MBall/Action/CopyFiles.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Action::CODE"
package MBall::Action::CODE;

@ISA = qw( MBall::Action );

use strict;

sub _init {
    my $self = shift;

    ## Hide this off where expand_macros won't trip over it.
    $self->{_CodeRef} = $self->{Action};
    $self->{Action} = "CODE";
}

sub execute {
    my $self = shift;

    $self->_log( "executing Perl code" );

    $self->{_CodeRef}->( $self );
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Action/CODE.pm"} = "inlined" if length "MBall/Action/CODE.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Action::AppendToFile"
package MBall::Action::AppendToFile;

@ISA = qw( MBall::Action );

use strict;

sub validate {
    my $self = shift;

    my @errors;

    eval { $self->SUPER::validate } or push @errors, $@;

    push @errors, $self->_fail_msg( "missing Path")
        unless exists $self->{Path};

    push @errors, $self->_fail_msg( "missing Body")
        unless exists $self->{Body};

    die @errors if @errors;

    1;
}


sub execute {
    my $self = shift;

    my $path = $self->Path;
    my $body = $self->Body;

    if ( $self->IfNotAlreadyInFile
        && 0 <= index $self->_os->read_file( $path ), $body
    ) {
        $self->_log( "already appended to file $path, not again" )
            if $self->_tracing;
        return;
    }

    $self->_os->append_to_file( $path, $body );
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Action/AppendToFile.pm"} = "inlined" if length "MBall/Action/AppendToFile.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Action"
package MBall::Action;

@ISA = qw( MBall::Object );

use strict;

sub new {
    my $proto = shift;
    my $action = { ref $_[0] ? %{shift()} : @_ };

    my $action_type;
    my $real_proto = $proto;

    if ( exists $action->{Action} && defined $action->{Action} ) {
        $action_type = ref $action->{Action} || $action->{Action};
        $real_proto .= "::$action_type";

        eval "require $real_proto" or die $@;
    }

    my $found = UNIVERSAL::isa( $real_proto, "MBall::Object" );
    $real_proto = $proto unless $found;
    
    my $self = bless $action, $real_proto;

    $self->_init;

    $self->{_Container}->_fail( "action field missing")
        unless exists $self->{Action} ;

    $self->{_Container}->_fail( "action field undefined")
        unless defined $self->{Action} ;

    $self->{_Container}->_fail( "unknown Action type $action->{Action}")
        unless $found;

    return $self;
}


sub definition_as_hash {
    my $self = shift;

    my %h;
    for ( keys %$self ) {
        next if /^_/;
        $h{$_} = $self->{$_};
    }

    return \%h;
}


sub _init {}


sub _id {
    my $self = shift;

    return $self->{_Container}->_id if $self->{_Container};
    return $self->SUPER::_id;
}


sub validate {
    my $self = shift;
    
    $self->_fail( "missing phase")
        unless exists $self->{Phase};

    1;
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Action.pm"} = "inlined" if length "MBall/Action.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Package"
package MBall::Package;

@ISA = qw( MBall::Object );

use strict;

use Carp qw( confess );
use File::Spec;
use MBall::Action;

sub empty {
    return ! grep defined $_ && length $_, @_;
}


sub new_subclass {
    my $proto = shift;
    my $pkg = { ref $_[0] ? %{$_[0]} : @_ };

    my $real_proto = $proto;
    $real_proto .= "::$pkg->{Style}" unless empty $pkg->{Style};

    my $found = UNIVERSAL::isa( $proto, "MBall::Object" );
    $real_proto = $proto unless $found;

    Package->new( $pkg )->_fail( "unknown style $pkg->{Style}" )
        unless $found;

    return $real_proto->new( @_ );
}


sub new {
    my $proto = shift;

    my $pkg = { ref $_[0] ? %{shift()} : @_ };
    my $self = bless $pkg, ref $proto || $proto;

    my $actions = delete $self->{Actions};

    my $inherited_actions = $self->Actions( { ExpandMacros => 0 } );

    ## CHEEZY HACK! TODO: implement fine-grained data inheritance.
    $self->{_HasCustomActions} = $actions && @$actions;
    if ( $inherited_actions ) {
        $self->add_action( $_ ) for @$inherited_actions;
    }

    if ( $actions ) {
        $self->add_action( $_ ) for @$actions;
    }

    return $self;
}


{
    ## In presentation order...
    my @_std_field_names = qw(
        Name
        Style
        Version
        MinVersion
        MaxVersion
        BuildDir
        ConfigParms
        DistName
        DistBaseName
        DistFileNames
        DistFiles
        URIs
        Requires
        ExternalComponents
    );

    my %_list_fields = map { ( $_ => undef ) } qw(
        ConfigParms
        DistFileNames
        DistFiles
        URIs
        Requires
        ExternalComponents
    );

    my $num = 0;
    my %_field_name_order = map {
        ( $_ => $num++ );
    } @_std_field_names;

    sub _field_display_position {
        my $self = shift;
        my ( $name ) = @_;
        return exists $_field_name_order{$name}
            ? $_field_name_order{$name}
            : $self->SUPER::_field_display_position( $name );
    }


    sub _field_names { shift->SUPER::_field_names( @_std_field_names, @_ ) }

    sub _is_list_field {
        my $self = shift;
        my ( $name ) = @_;
        return
            exists $_list_fields{$name} || $self->SUPER::_is_list_field( $name);
    }
}


sub validate {
    my $self = shift;

    my @errors;
    for ( $self->_field_names ) {
        my $ok = eval { [ $self->$_( { ExecuteSubs => 0 } ) ] };
        push @errors, $@ unless $ok || $@ =~ /Can't locate object method/;
    }

    if ( $self->{Actions} ) {
        $_->validate for @{$self->{Actions}};
    }

    die @errors if @errors;
}


sub add_action {
    my $self = shift;

    my $action = UNIVERSAL::isa( $_[0], "MBall::Action" )
        ? shift
        : MBall::Action->new( 
            %{shift()},
            _Container => $self,
        );

    ## Really, we need a whole slew of actions.  Well, *really* we need
    ## a full programming environment.
    push @{$self->{Actions}}, $action;
}


sub definition_as_hash {
    ## Return a simple HASH that's good for loading in to an install script
    ## We need to expand any and all variables that are needed in order
    ## to prevent the install script from trying to use finders.
    my $self = shift;

    my %h;
    my %keys = map { $_ => 0 } grep !/^_/, keys %$self;

    ## Expand anything that doesn't depend (directly or indirectly) on
    ## one of the file roots.
    $keys{$_} = "expand" for qw(
        Version
        MinVersion
        DistFileNames
        DistBaseName
        Requires
        DetectedRequires
    );

    ## NOTE: DetectedRequires does $pkg->configure in PerlModules, which
    ## does $_->install for all Requires.  What a pain.
Carp::cluck( "!!!", $self->{Name} );
    for ( keys %keys ) {
        next if /^_/;


        if ( $keys{$_} ) {
            my @v = $self->$_();
            $h{$_} = @v == 1 ? $v[0] : \@v
                if @v && defined $v[0];
        }
        else {
            $h{$_} = $_ ne "Actions"
                 ? $self->{$_}
                : [ map $_->definition_as_hash, @{$self->{$_}} ];
        }
    }

    for ( qw( DetectedRequires Requires ) ) {
        $h{$_} = [] unless exists $h{$_};
    }

    return \%h;
}


sub _finder {
    my $self = shift;
    my ( $name ) = @_ ;

    my $real_name = $name;
    $real_name = "MBall::Finder::$name"
        unless $real_name->isa( "MBall::Finder" );

    undef $@;
    eval "require $real_name";
    Carp::confess $@ if defined $@ and length $@;

    return $self->{_Container}->{_Finders}->{$real_name} ||=
        $real_name->new( _Container => $self->{_Container} );
}


## TODO: Validate things.... (i.e. call this in _get)
sub validate_Version {
    my $self = shift;
    my ( $name, $value ) = @_;
    ## protect against perl vstring madness
    $self->_log( "version number contains non-printables" )
        if $value =~ /[\000-\037]/;
}


sub _get_sentinal_file_name {
    my $self = shift;
    my $action = @_ ? shift : (caller(1))[3];
    
    $action =~ s/.*_//;

    ## Hmm, we could add a layer of indirection by using a macro that defines
    ## the sentinal macro name, but EOBFUSCATED
    confess "No action passed" unless defined $action && length $action;

    $action =~ s/.*:://;

    my $sentinal_macro_name = "\u${action}SentinalFile";

    my $sentinal_file_name = $self->$sentinal_macro_name();

    if ( ! defined $sentinal_file_name ) {
        ## This is lazy defaulting of a macro.  That's a bit of
        ## a hack, but otherwise I need to be diligent about defining all
        ## action sentinals up front, as do our users.  Perlhaps a "*"
        ## syntax in macro names.
        ##
        ## local won't cut it here because we need to delete() if it is undef,
        ## and autovivification screws that up. So we save it in a temp var.
        my $prev_sm = $self->{$sentinal_macro_name};
        $self->{$sentinal_macro_name} =
            "<%MetaInfoDir%>/<%DistBaseName%>_${action}_done";

        $sentinal_file_name = $self->$sentinal_macro_name();

        if ( defined $prev_sm ) {
            $self->{$sentinal_macro_name} = $prev_sm;
        }
        else {
            delete $self->{$sentinal_macro_name};
        }
    }

    $self->_fail( "couldn't determine the sentinal filename for $action" )
        unless defined $sentinal_file_name;

    return $sentinal_file_name;
}


sub _get_sentinal_file_mtime { ## TODO: get rid of this
    my $self = shift;
    my $action = @_ ? shift : (caller(1))[3];

    $action =~ s/.*_//;

    return $self->_os->mtime( $self->_get_sentinal_file_name( $action, @_ ) );
}


sub _get_file_info {
    my $self = shift;
    my ( $id, $fn ) = @_;

    return {
        id       => $id,
        path     => $fn,
        mtime    => $self->_os->mtime( $fn ),
    };
}


sub _get_sentinal_info {
    my $self = shift;
    my $action = @_ ? shift : (caller(1))[3];

    $action =~ s/.*_//;

    my $fn = $self->_get_sentinal_file_name( $action, @_ );

    return $self->_get_file_info( "$action sentinal" => $fn );
}


sub _get_info {
    my $self = shift;
    my ( $id ) = @_;

    return $self->_get_file_info( $id => $self->$id() );
}


sub touch_sentinal_file {
    my $self = shift;
    my $action = @_ ? shift : (caller(1))[3];

    $action =~ s/.*_//;

    $self->_os->touch(
        $self->_get_sentinal_file_name( $action, @_ ),
        { Silent => 1 }
    );
}


sub rm_sentinal_file {
    my $self = shift;
    my $action = @_ ? shift : (caller(1))[3];

    $action =~ s/.*_//;

    $self->_os->rm_if_present(
        $self->_get_sentinal_file_name( $action, @_ ),
        {
            Silent => 1,
        }
    );
}


sub _prereqs_ok {
    my $self = shift;

    my $prev = shift;

    if ( $prev->{mtime} < 0 ) {
        $self->_log( "$prev->{id} is missing" );
        return;
    }

    for ( @_ ) {
        if ( $_->{mtime} < 0 ) {
            $self->_log( "$_->{id} is missing" );
            return;
        }

        if ( $prev->{mtime} > $_->{mtime} ) {
            $self->_log( "prereq $prev->{id} is newer than $_->{id}" );
            return;
        }

        $self->_log( "prereq ok: $prev->{id} exists and is not newer than $_->{id}" )
            if $self->_tracing;

        $prev = $_;
    }

    return 1;
}


##
## Actions
##
sub cd_to {
    my $self = shift;
    my ( $name ) = @_;

    my $path = $self->$name();

    $self->_fail(
        " $name not defined at ",
        (caller)[1],
        ", line ",
        (caller)[2]
    ) unless defined $path;

    $self->_os->mkpath( $path );

    $self->_os->cd( $name =>
        $self->$name() || $self->_fail( "$name not defined" )
    );
}


sub _parse_ver {
    my $self = shift;
    my ( $ver ) = @_;

    # A "seg pair" is a /\d+\D+/, so version numbers like "1a" can be
    # coped with.
    my @seg_pairs = split /[^0-9a-bA-B]/, $ver, -1;
    $self->_fail( "can't parse version number '$ver'" )
        if ! @seg_pairs || grep !length, @seg_pairs;
    pop @seg_pairs while @seg_pairs && !$seg_pairs[-1];
    return map /(\d*)(.*)/, @seg_pairs;
}


sub _cmp_ver {
    my $self = shift;
    my ( $vera, $verb ) = @_;

    my @a_segs = $self->_parse_ver( $vera );
    my @b_segs = $self->_parse_ver( $verb );

    while ( @a_segs && @b_segs ) {
        my ( $a_seg, $b_seg ) = ( shift @a_segs || 0, shift @b_segs || 0 );
        my $a_is_numeric = $a_seg =~ /^(\d+(\.\d+)?|\.\d+)\z/;
        my $b_is_numeric = $b_seg =~ /^(\d+(\.\d+)?|\.\d+)\z/;
        if ( $a_is_numeric && $b_is_numeric ) {
            return $a_seg <=> $b_seg || next;
        }
        elsif ( ! $a_is_numeric && ! $b_is_numeric ) {
            return $a_seg cmp $b_seg || next;
        }
        else {
            $self->_fail( 
                "can't compare incompatible version numbers '$vera' and '$verb'"
            );
        }
    }
    return @a_segs <=> @b_segs;
}


## TODO: move these to an OSDriver? Implement the tools concept?
sub _validate_tgz_file {
    my $self = shift;
    my ( $fn ) = @_;

    return 0 unless -e $fn;

    open FH, "<$fn"       or $self->_fail( "$! opening $fn" );
    my $first_few;
    my $count = read FH, $first_few, 2;
    $self->_fail( "$! reading $fn") unless defined $count;
    close FH               or $self->_fail( "$! closing $fn");

    return 0 unless $count >= 2 && unpack( "H4", $first_few ) eq "1f8b";

    ## TODO: Use Compress::Zlib
    return 0 unless eval {
        $self->_run_build_cmd( "gunzip -dt $fn" );
        1
    };

    return 1;
}


sub _validate_tar_gz_file { shift->_validate_tgz_file( @_ ) }


sub _validate_tar_file {
    my $self = shift;
    my ( $fn ) = @_;

    return 0 unless -e $fn;

    open FH, "<$fn"       or $self->_fail( "$! opening $fn" );
    my $first_few;
    my $count = read FH, $first_few, 263;
    $self->_fail( "$! reading $fn") unless defined $count;
    close FH               or $self->_fail( "$! closing $fn");

    return 0 unless $count >= 2 && substr( $first_few, -6) eq "ustar\000";

    return 0 unless eval {
        $self->_run_build_cmd( "tar tf $fn >/dev/null" );
        1
    };

    return 1;
}


sub validate_files {
    my $self = shift;

    ## TODO: use File::Type or File::MimeMagic
    grep {
        my $found = -e $_;

        my $is_valid;

        if ( $found ) {
            $is_valid =
              /\.tar\.gz\z/ ? $self->_validate_tar_gz_file( $_ )
            : /\.tgz\z/     ? $self->_validate_tgz_file( $_ )
            : /\.tar\z/     ? $self->_validate_tar_file( $_ )
            : $self->_fail( "can't validate file $_, unknown type" );

            $self->_log( "$_ ", ! $is_valid ? "not " : (), "valid" )
                if $self->_tracing;
        }
        else {
            $self->_log( "$_ not found" )
                if $self->_tracing;
        }

        $is_valid;
    } @_;
}


sub validate_or_unlink_files {
    my $self = shift;
    grep {
        my $is_valid = scalar $self->validate_files( $_ );
        $self->_os->rm( $_ ) if !$is_valid && -e $_;
        $is_valid
    } @_;
}


sub _unpack_tar_gz_file {
    my $self = shift;
    my ( $fn ) = @_;
    $self->_run_build_cmd( "gunzip -c $fn | tar xf -" );
}

sub _unpack_tgz_file { shift->_unpack_tar_gz_file( @_ ) }

sub _unpack_tar_file {
    my $self = shift;
    my ( $fn ) = @_;
    $self->_run_build_cmd( "tar xf $fn" );
}


sub unpack_file {
    my $self = shift;

    my ( $fn ) = @_;

    for ( $fn ) {
        unless ( eval {
            /\.tar\.gz\z/ ? $self->_unpack_tar_gz_file( $_ )
            : /\.tgz\z/   ? $self->_unpack_tgz_file( $_ )
            : /\.tar\z/   ? $self->_unpack_tar_file( $_ )
            : $self->_fail( "can't validate file $_, unknown type" );
            1;
        } ) {
            my $x = $@;
            eval {$self->_os->rmtree( $self->BuildDir ) };
            die $x;
        }
    }
}

my $INT_caught = 0;
sub _catch_INT { $INT_caught = 1; }

sub _execute_actions {
    my $self = shift;
    my $action = @_ ? shift : (caller(1))[3];

    $action =~ s/.*_//;

    my $actions = $self->Actions( { ExpandMacros => 0 } );

    for ( @$actions ) {
        if ( lc $_->{Phase} eq $action ) {
            $_->{_OSDriver} = $self->_os;
            $self->cd_to( "BuildDir" );
            $_->execute;
            delete $_->{_OSDriver};
        }
    }
}


## check_foo subs check to see if foo or it's prerequisites need to be done
## and return a list of CODE refs to be executed to actuall do it.  A full
## list is returned to make a "force" option easy to implement by executing
## each sub in an eval {...}.

sub check_download {
    my $self = shift;

        ## All checks are cached in order to avoid redundant operations.
        ## No credit cards are accepted, however.
        ##
        ## Init this on entry if this is the first call on the stack, this
        ## is actually done so that _CachedChecks will be cleaned up on
        ## exit.  A central structure is used so that the lifetime of the
        ## cache is 
        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

        ## If the previous invocation returned something, then this invocation
        ## must also return something.  However, we can just return a NOOP because
        ## if any work needs to be done, it will already have been done.
        ## TODO: Perhaps implement the check results as a queue and implement
        ## a "don't do more
        ## queue
        return $self->{_Container}->{_CachedChecks}->{$self}->{download}
            if $self->{_Container}->{_CachedChecks}->{$self}->{download};

    $self->_log( "checking download" )
        if $self->_tracing >= 2;

    unless ( $self->IsVirtual ) {
        my @dist_files = $self->DistFiles;
        my @valid_distfiles = $self->validate_or_unlink_files( @dist_files );
        return if @valid_distfiles;
    }

    $self->_log( "download needed!" )
        if $self->_tracing;

    return $self->{_Container}->{_CachedChecks}->{$self}->{download} = sub {
        ## This is tricky: some ops may be run at check_ time by
        ## DetectedRequires, since these require that PerlModules be configured
        ## (and thuse downloaded and unpacked).  So only run it once, but leave
        ## the same CODE ref in "download" so that future check_...s on this
        ## package are bypassed and use the boolean flag "downloaded" to prevent
        ## repeat downloads.
        ##
        ## This flag will be cleared at the end of the current top-level command
        ## (see the local ... if bit above.
        $self->_do_download
            unless $self->{_Container}->{_CachedChecks}->{$self}->{downloaded}++;
    };
}


sub download {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    $_->() for $self->check_download
}


sub redownload {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    ## In general, we want to call the check_ to make sure all prereqs are
    ## in order.  Not so important for download, but for later stages.
    my @subs = $self->check_download;
    push @subs, sub { $self->_do_download( @_ ) } unless @subs;
    $_->() for @subs;
}


sub _do_download {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    if ( $self->IsVirtual ) {
        $self->_execute_actions;
        return;
    }

    my @distfiles = $self->DistFiles;
    $self->rm( $_ ) for grep -f $_, @distfiles;

    $self->_log( "downloading" )
        if $self->_tracing;

    $self->cd_to( "DistsDir" );

    my @uris = $self->URIs;

    my $downloaded_fn;
    my @errors;
    $DB::single = 1;
    if ( @uris ) {
        $downloaded_fn = eval {
            $self->_os->download( \@uris );
        };
        push @errors, $@ unless defined $downloaded_fn;
    }
    else {
        ## TODO: pass in the validation sub, or let the OSDriver perform
        ## validation.
        for ( $self->FinderClasses ) {
            $downloaded_fn = eval {
                $self->_finder( $_ )->download( $self->Name, \@uris );
            };
            last if defined $downloaded_fn;
            chomp $@;
            push @errors, $@;
        }
    }


    unless ( defined $downloaded_fn ) {
        my $distfiles =
            ! @distfiles      ? "no distribution files defined, can't download or build."
            : @distfiles == 1 ? "unable to download $distfiles[0], please fix or download manually."
            : @distfiles == 2 ? "unable to download $distfiles[0] or $distfiles[1], please fix or download manually."
            : "unable to download any of " . join( ", ", @distfiles ) . ", please fix or download manually.";
        $self->_fail( $distfiles, "\n", @errors );
    }

    @distfiles = map scalar File::Basename::fileparse( $_ ), @distfiles;

    unless ( grep $downloaded_fn eq $_, @distfiles ) {
        $self->_fail(
            "downloaded file name $downloaded_fn is not an expected distribution file name.  Must be one of ", join ", ", map "'$_'", @distfiles
        );
    }
}


sub _find_distfile {
    my $self = shift;

    ## TODO: use the newest distfile
    return (grep -e, $self->DistFiles )[0];
}


sub check_unpack {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

        return $self->{_Container}->{_CachedChecks}->{$self}->{unpack}
            if $self->{_Container}->{_CachedChecks}->{$self}->{unpack};

    $self->_log( "checking unpack" )
        if $self->_tracing >= 2;

    my $builddir_i = $self->_get_info( "BuildDir" );

    if ( defined $builddir_i->{path} && -f $builddir_i->{path} ) {
        $self->_log( "BuildDir seems to be a file, removing" );
        $self->_os->rm( $builddir_i->{path} );
        $builddir_i = $self->_get_info( "BuildDir" );
    }

    my $sentinal_i = $self->_get_sentinal_info;

    my $builddir_found = $self->_prereqs_ok( $builddir_i );
    my $sentinal_found = $self->_prereqs_ok( $sentinal_i );

    if ( $builddir_found ) {
        if ( ! $sentinal_found ) {
            my @entries = $self->_os->read_dir( $builddir_i->{path} );
            if ( @entries ) {
                ## BuildDir ok, and not empty, but no sentinal file.
                ## Sentinal file is needed when the package is virtual and is used
                ## by later stages.  The BuildDir may be updated since the unpack and
                ## those later stages should pay attention to the sentinal file instead.
                $self->_log( "unpack sentinal file not found, assuming manual unpack was done" );
                $self->_os->touch(
                    $builddir_i->{mtime},
                    $builddir_i->{mtime},
                    $sentinal_i->{path}
                );
            }
            else {
                $self->_log( "empty build dir and no unpack sentinal found, removing" );
                $self->_os->rmtree( $builddir_i->{path} );
                $builddir_i = $self->_get_info( "BuildDir" );
                $builddir_found = 0;
            }
        }

        my $distfile = $self->_find_distfile;

        ## BuildDir but no distfile is OK.
        return if $builddir_found && ! defined $distfile;

        if ( defined $distfile ) {
            my $distfile_i = $self->_get_file_info( "DistFile" => $distfile );
            return if $self->_prereqs_ok( $distfile_i => $sentinal_i );
        }
    }

    ## No need to ponder a download if the BuildDir is ok, only worry
    ## about it if !BuildDir.  This lets people (I hope) distribute an unpacked
    ## but not configured tarball.
    my @subs = $self->check_download;

    $self->_log( "unpack needed!" )
        if $self->_tracing;

    return @subs, $self->{_Container}->{_CachedChecks}->{$self}->{unpack} = sub {
        $self->_do_unpack
            unless $self->{_Container}->{_CachedChecks}->{$self}->{unpacked}++;
    };
}


sub unpack {
    my $self = shift;
    
        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    $_->() for $self->check_unpack;
}


sub reunpack {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    my @subs = $self->check_unpack;
    push @subs, sub { $self->_do_unpack( @_ ) } unless @subs;
    $_->() for @subs;
}


sub _do_unpack {
    my $self = shift;

    my $builddir = $self->BuildDir;

    unless ( $self->IsVirtual ) {
        $self->_os->rmtree( $builddir );

        my $distfile = $self->_find_distfile;

        unless ( $distfile ) {
            $self->_fail(
                "no DistFiles found to unpack, checked for ",
                join ", ", $self->DistFiles
            );
        }

        $self->_log( "unpacking" )
            if $self->_tracing;

        $self->rm_sentinal_file;

        my $unpack_tmp_dir = $self->UnpackTmpDir;
        $self->_os->rmtree( $unpack_tmp_dir );
        $self->cd_to( "UnpackTmpDir" );

        unless ( eval { $self->unpack_file( $distfile ); 1 } ) {
            my $x = $@;
            $self->_os->rmtree( $unpack_tmp_dir );
            die $x;
        }

        ## See whether this created a file, a directory, or several directories
        my @entries = $self->_os->read_dir( $unpack_tmp_dir );

        $self->_fail(
            "unpacking DistFile $distfile in to $unpack_tmp_dir did not do anything.",
        ) unless @entries;

        my $dist_dir;

        ## TODO: give derived packages more control over this process, perhaps
        ## by using a help method they can override.  Right now, they can
        ## override unpack() or mess with <%BuidDir%> and <%UnpackTmpDir%>.
        if ( @entries == 1 && -d $entries[0] ) {
            ## It was a tarball-like thing and unpacked in to a directory, we
            ## hope (ie it unpacked in to a build directory as opposed to
            ## having the directory *be* the payload.  TODO: maybe allow this
            ## "it's the build dir" assumption to be overridden, and maybe
            ## enable some smarter guessing, esp. in the case of .zip files
            ## where this guess is so often wrong.
            ## TODO: offer a feature not to change the name, other packages
            ## may be looking for a directory, like mod_perl looks for
            ## apache dirs matching a certain name.  This would mean that
            ## we need to save the dist name in the metainfo dir, since there's
            ## no earthly way we could intuit it.  Hmm, that would mean the
            ## BuildDir target could go undefined until we have it, which could
            ## be a boon, and a detriment.
            $self->_os->move_dir( $entries[0], $builddir );

            ## Leave the unpack dir around for later use, this is the common
            ## case when dealing with open source-style tarballs.
        }
        else {
            ## it created either only a single file or multiple dir entries;
            ## put any/all of the above in a dir we name.
            $self->_os->move_dir( $unpack_tmp_dir, $builddir );
        }
    }

    $self->_execute_actions;

    $self->touch_sentinal_file;
}


sub _run_build_cmd {
    my $self = shift;
    my ( @cmd ) = @_;

    return unless @cmd;

    my $buildenv = $self->BuildEnv( { Flatten => 0 } );

    my %saved_env = %ENV;

    my $ok = eval {
        if ( $buildenv && keys %$buildenv ) {
            ## TODO: join with pathsep for ARRAYs
            $ENV{$_} = $buildenv->{$_}
                for keys %$buildenv;
        }

        $self->_os->run( @cmd );

        1;
    };

    %ENV = %saved_env;

    die $@ unless $ok;
}


sub _run_build_cmd_and_capture_stdout {
    my $self = shift;
    my ( @cmd ) = @_;

    return unless @cmd;

    my $buildenv = $self->BuildEnv( { Flatten => 0 } );

    my %saved_env = %ENV;

    my $stdout = eval {
        if ( $buildenv && keys %$buildenv ) {
            ## TODO: join with pathsep for ARRAYs
            $ENV{$_} = $buildenv->{$_}
                for keys %$buildenv;
        }

        $self->_os->run_and_capture_stdout( @cmd );
    };

    %ENV = %saved_env;

    die $@ unless defined $stdout;

    return $stdout;
}


## default build is for "configure" packages
sub _do_configure_cmd {
    my $self = shift;

    $self->_run_build_cmd( $self->ConfigCmd );
}


sub check_configure {
    my $self = shift;
    
        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

        ## If the previous invocation returned something, then this invocation
        ## must also return something.  However, we can just return a NOOP because
        ## if any work needs to be done, it will already have been done.
        ## TODO: Perhaps implement the check results as a queue and implement
        ## a "don't do more
        ## queue
        return $self->{_Container}->{_CachedChecks}->{$self}->{configure}
            if $self->{_Container}->{_CachedChecks}->{$self}->{configure};

    $self->_log( "checking configure" )
        if $self->_tracing >= 2;

    my @subs;
    my $sentinal_i = $self->_get_sentinal_info;

    {
        ## Check Required prereqs first so they get unpacked first.  Principle of
        ## least surprise for end users staring at logging output,
        ## doesn't matter technically.
        my @r = map $self->{_Container}->get_package( $_, $self ), $self->Requires;

        $self->_log( "Requires: ", join ", ", map $_->Name, @r )
            if $self->_tracing && @r;

        push @subs, $_->check_install for @r;
    }

    my @unpack_subs = $self->check_unpack;

    unless ( @subs || @unpack_subs ) {
        my $sentinal_i     = $self->_get_sentinal_info;
        my $unp_sentinal_i = $self->_get_sentinal_info( "unpack" );
        return if $self->_prereqs_ok( $unp_sentinal_i, $sentinal_i );
    }

    ## no need to unpack these unless we're going to configure...
    my @r = map
        $self->{_Container}->get_package( $_ ),
        $self->ExternalComponents;

    $self->_log( "ExternalComponents: ", join ", ", map $_->Name, @r )
        if $self->_tracing && @r;

    for ( @r ) {
        push @subs, $_->check_unpack;

        ## TODO: restructure the macros so components may be placed in
        ## wherever needed, including within $self's BuildDir
    }

    $self->_log( "configure needed!" )
        if $self->_tracing;

    return @subs, @unpack_subs,
        $self->{_Container}->{_CachedChecks}->{$self}->{configure} = sub {
            $self->_do_configure
                unless $self->{_Container}->{_CachedChecks}->{$self}->{configured}++;
        };
}


sub configure {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    $_->() for $self->check_configure
}


sub reconfigure {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    my @subs = $self->check_configure;
    push @subs, sub { $self->_do_configure( @_ ) } unless @subs;
    $_->() for @subs;
}


sub _do_configure {
    my $self = shift;
    my $options = @_ && ref $_[-1] ? pop : {};

    unless ( $self->IsVirtual ) {
        $self->_log( "configuring" )
            if $self->_tracing;

        $self->rm_sentinal_file;

        $self->cd_to( "BuildDir" );

        $self->_do_configure_cmd( $self->ConfigParms );
    }

    $self->_execute_actions     unless $options->{TestConfigure};
    $self->touch_sentinal_file  unless $options->{TestConfigure};
}


sub check_make {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

        ## If the previous invocation returned something, then this invocation
        ## must also return something.  However, we can just return a NOOP because
        ## if any work needs to be done, it will already have been done.
        ## TODO: Perhaps implement the check results as a queue and implement
        ## a "don't do more
        ## queue
        return $self->{_Container}->{_CachedChecks}->{$self}->{make}
            if $self->{_Container}->{_CachedChecks}->{$self}->{make};

    $self->_log( "checking make" )
        if $self->_tracing >= 2;

    my @subs;

    {
        ## Some requirements need to be detected by unpacking the
        ## distro and doing a test-make.
        ## Queue these first under principle of least surprise for end users
        ## staring at logging output.
        my @r = map
            $self->{_Container}->get_package( $_ ),
            $self->DetectedRequires;

        $self->_log( "DetectedRequires: ", join ", ", map $_->Name, @r )
            if $self->_tracing && @r;

        push @subs, $_->check_install for @r;
    }

    push @subs, $self->check_configure;

    unless ( @subs ) {
        my $sentinal_i      = $self->_get_sentinal_info;
        my $conf_sentinal_i = $self->_get_sentinal_info( "configure" );
        return if $self->_prereqs_ok( $conf_sentinal_i => $sentinal_i );
    }

    $self->_log( "make needed!" )
        if $self->_tracing;

    return @subs, $self->{_Container}->{_CachedChecks}->{$self}->{make} = sub {
        $self->_do_make
            unless $self->{_Container}->{_CachedChecks}->{$self}->{made}++;
    };
}


sub make {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    $_->() for $self->check_make;
}


sub remake {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    my @subs = $self->check_make;
    push @subs, sub { $self->_do_make( @_ ) } unless @subs;
    $_->() for @subs;
}

sub _do_make {
    my $self = shift;

    unless ( $self->IsVirtual ) {
        $self->_log( "making" )
            if $self->_tracing;

        $self->rm_sentinal_file;

        $self->cd_to( "BuildDir" );

        $self->_run_build_cmd( qw( make ) );

        $self->_execute_actions;
        $self->touch_sentinal_file;
    }
}


sub _do_test_cmd {
    my $self = shift;

    $self->_run_build_cmd( $self->TestCmd );
}


sub check_test {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

        ## If the previous invocation returned something, then this invocation
        ## must also return something.  However, we can just return a NOOP because
        ## if any work needs to be done, it will already have been done.
        ## TODO: Perhaps implement the check results as a queue and implement
        ## a "don't do more
        ## queue
        return $self->{_Container}->{_CachedChecks}->{$self}->{test}
            if $self->{_Container}->{_CachedChecks}->{$self}->{test};

    $self->_log( "checking test" )
        if $self->_tracing >= 2;

    my @subs = $self->check_make;

    unless ( @subs ) {
        my $sentinal_i      = $self->_get_sentinal_info;
        my $make_sentinal_i = $self->_get_sentinal_info( "make" );
        return if $self->_prereqs_ok( $make_sentinal_i => $sentinal_i );
    }

    $self->_log( "test needed!" )
        if $self->_tracing;

    return @subs, $self->{_Container}->{_CachedChecks}->{$self}->{test} = sub {
        $self->_do_test
            unless $self->{_Container}->{_CachedChecks}->{$self}->{tested}++;
    }
}


sub test {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    $_->() for $self->check_test;
}


sub retest {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    my @subs = $self->check_test;
    push @subs, sub { $self->_do_test( @_ ) } unless @subs;
    $_->() for @subs;
}


sub _do_test {
    my $self = shift;

    unless ( $self->IsVirtual ) {
        $self->_log( "testing" )
            if $self->_tracing;

        $self->rm_sentinal_file;

        $self->_do_test_cmd;
    }

    $self->_execute_actions;
    $self->touch_sentinal_file;
}


sub query_installed_version {
    my $self = shift;

    my $version_cmd = $self->InstalledVersionQuery;

    my $output = eval {
        ## A failure to run begets a "" version string
        $self->_run_build_cmd_and_capture_stdout( $version_cmd );
    };
    return "" unless defined $output;
    chomp $output;
    return $output;
}


sub check_install {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

        ## If the previous invocation returned something, then this invocation
        ## must also return something.  However, we can just return a NOOP because
        ## if any work needs to be done, it will already have been done.
        ## TODO: Perhaps implement the check results as a queue and implement
        ## a "don't do more
        ## queue
        return $self->{_Container}->{_CachedChecks}->{$self}->{install}
            if $self->{_Container}->{_CachedChecks}->{$self}->{install};

    $self->_log( "checking install" )
        if $self->_tracing >= 2;

    my $installedversion = $self->query_installed_version;

    my $minversion = $self->MinVersion;

    ## If the onhand version is newer, might as well install it.
    my $version = $self->Version;
    $minversion = $version
        if ! defined $minversion
            || ( defined $version
               && $self->_cmp_ver( $version, $minversion ) > 1
            );

    my $do_it;
    if ( ! defined $installedversion || ! length $installedversion ) {
        $self->_log( "# no installed version detected" );
        $do_it = 1;
    }
    elsif ( $self->_cmp_ver( $installedversion, $minversion ) < 0 ) {
        $self->_log( "# $installedversion < $minversion" );
        $do_it = 1;
    }
    else {
        $self->_log( "# $installedversion >= $minversion, not upgrading" );
    }

    return unless $do_it;

    my @subs = $self->check_test;

    $self->_log( "install needed!" )
        if $self->_tracing;

    return @subs, $self->{_Container}->{_CachedChecks}->{$self}->{install} = sub {
        $self->_do_install
            unless $self->{_Container}->{_CachedChecks}->{$self}->{installed}++;
    };
}

## TODO: in one scenario, we should only do lots of work if the main package
## needs to be installed.
## In another, this may be true for each of several
## packages independantly of eachother.
## In a third, we may want to install some packages if any prereqs
## need to be upgraded or if needs to be upgraded.
##
## May want to just script it, or we may want to allow each package to have
## some requirements (like MinVersion).  May also need to encode some
## logic, like "reconfigure if this is upgraded" so that a newly installed
## optional prereq can be discovered.  Hmmm, hmmm, hmmm.

sub install {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    $_->() for $self->check_install;
}

sub reinstall {
    my $self = shift;

        local $self->{_Container}->{_CachedChecks} = {}
            unless $self->{_Container}->{_CachedChecks};

    my @subs = $self->check_install;
    push @subs, sub { $self->_do_install( @_ ) } unless @subs;
    $_->() for @subs;
}


sub _do_install {
    my $self = shift;

    unless ( $self->IsVirtual ) {
        $self->_log( "installing" )
            if $self->_tracing;

        $self->rm_sentinal_file;

        $self->cd_to( "BuildDir" );
        $self->_run_build_cmd(
            $self->InstallAsRoot
                ? qw{su -m root -c "make install"}
                : qw{make install}
        );
    }

    $self->_execute_actions;

    ## NOTE: as of this writing, this sentinal is not used internally;
    ## it's advisory only!
    $self->touch_sentinal_file;

}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Package.pm"} = "inlined" if length "MBall/Package.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::OSDriver"
package MBall::OSDriver;

@ISA = qw( MBall::Object );

use strict;

use Cwd ();
use File::Basename ();
use File::Path ();


sub new {
    my $self = shift->SUPER::new( @_ );

    $self->{_StartupDir} = Cwd::cwd();

    return $self;
}


sub _set_caller {
    my $self = shift;
    $self->{Caller} = shift;
}


sub _get_caller { shift->{Caller} }


sub _id {
    my $self = shift;
    my $c = $self->_get_caller;
    return $c ? $c->_id( @_ ) : $self->SUPER::_id( @_ );
}


sub _fail_errno {
    my $self = shift;
    $self->_fail( ": ", $! ) unless @_;
    $self->_fail( ": $!", @_ ? ( " while ", @_ ) : () );
}


sub find_plugins {
    my $self = shift;

    my ( @namespaces ) = @_;

    my @plugins;

    for my $ns ( @namespaces ) {
        my @ns_dirs = File::Spec->catdir( split /::/, $ns );

        my %files;
        for my $inc_dir ( @INC ) {
            $inc_dir = File::Spec->rel2abs(
                $inc_dir,
                $self->{_StartupDir}
            );
            my $dir = File::Spec->catdir( $inc_dir, @ns_dirs );
            next unless -e $dir;
            unless ( -d $dir ) {
                warn "$dir is not a directory\n";
                next;
            }
            $files{$_} ||= 1
                for grep -f && /\.pm$/, $self->read_dir( $dir );
        }

        for ( sort keys %files ) {
            s/\.pm$//;
            $_ = $ns . "::" . File::Basename::basename $_;
            eval "require $_" or die $@;
        }

        no strict "refs";
        push @plugins, 
            grep $_->can( "new" ),
                map {
                    s/::$//;
                    "${ns}::$_";
                } keys %{$ns . "::"}

    }

    return @plugins;
}



sub download {
    my $self = shift;
    my ( $uris, $dest ) = @_;

    unless ( defined $dest ) {
        ## TODO: use URI; here.
        ## TODO: Have the URIs paired with the distfiles.
        $dest = $uris->[0];
        $dest =~ s/[?#].*//;
        ## TODO: check that $dest is a valid distfile
        $dest = File::Basename::fileparse( $dest );
        $self->_fail( "couldn't parse a filename out of '$uris->[0]'" )
            unless length $dest;
    }

    $self->mkparentpath( $dest );

    my @tried;
    my @errors;

    if ( $self->{_Downloaders} ) {
        ## Use the existing downloaders first
        for my $dl ( @{$self->{_Downloaders}} ) {
            for my $uri ( @$uris ) {
                my $dl_fn = eval {
                    $dl->download( $uri, $dest );
                };
                return $dl_fn if defined $dl_fn;
                push @errors, $@;
            }
            push @tried, ref $dl;
            $tried[-1] =~ s/.*:://;
        }
    }

    unless ( $self->{_ScannedDownloaders} ) {
        $self->{_DownloaderClasses} = [
            $self->find_plugins( "MBall::Downloader" )
        ];
        $self->{_ScannedDownloaders} = 1;
    }

    ## Try a few other downloaders
    while ( my $dlc = shift @{$self->{_DownloaderClasses}} ) {
        my $dl = $dlc->new( _Container => $self );
        unless ( $dl->available ) {
            push @tried, $dlc;
            $tried[-1] =~ s/.*:://;
            next;
        }
        push @{$self->{_Downloaders}}, $dl;

        for my $uri ( @$uris ) {
            my $dl_fn = eval {
                $dl->download( $uri, $dest );
                ## TODO: validate here.  Use callback?
            };
            return $dl_fn if defined $dl_fn;
            push @errors, $@;
        }
    }

    $self->_fail(
        "Can't download file:\n",
        map { chomp; s/^    //g; "$_\n" } @errors
    ) if @errors;

    $self->_fail(
        "can't find any way to download $dest, tried ", join ", ", @tried
    );
}


sub can_gunzip {
    my $self = shift;

    return 1;
}


sub gunzip {
    my $self = shift;

    my ( $fn ) = @_;

    $self->run( "gzip", "-d", $fn );
}


#sub _get_saved_original_location {
#    my $self = shift;
#    my ( $fn ) = @_;
#
#    ## This should work on most filesystems, I hope.
#    my ( $work_vol, $work_dirs, undef ) =
#        File::Spec->splitpath( $self->OriginalsPath );
#    my ( $file_vol, $file_dirs, $file_fn ) =
#        File::Spec->splitpath( $fn );
#
#    my $save_fn = File::Spec->catpath(
#         $work_vol,
#         File::Spec->catdirs( $work_dirs, $file_dirs ),
#         $file_fn
#    );
#
#    return $save_fn;
#}
#
#
#sub _get_saved_original_meta_location {
#    my $self = shift;
#    my ( $fn ) = @_;
#
#    ## This should work on most filesystems, I hope.
#    my ( $work_vol, $work_dirs, undef ) =
#        File::Spec->splitpath( $self->MetaInfoDir );
#    my ( $file_vol, $file_dirs, $file_fn ) =
#        File::Spec->splitpath( $fn );
#
#    ## This is not foolproof due to pathlength limitations.  But it
#    ## should suffice for now; I *don't* want to mess with hashing
#    ## the filename for now, it makes it too hard to debug.
#    my $save_fn = File::Spec->catpath(
#        $work_vol,
#        File::Spec->catdir( $work_dirs, $file_dirs ),
#        $file_fn
#    );
#
#    return $save_fn;
#}
#
#
sub copy_file {
    my $self = shift;
    my ( $from_fn, $to_fn ) = @_;

    $self->mkparentpath( $to_fn );

    $self->_log_cmd( "cp '$from_fn' '$to_fn'" );

    require File::Copy;
    File::Copy::syscopy( $from_fn, $to_fn );

    my @stats = stat $from_fn;

    ## TODO: test and cope with symlinks
}


sub store_perldata {
    my $self = shift;
    my ( $fn, $structure ) = @_;

    $self->mkparentpath( $fn );

    open FH, ">$fn"              or $self->_fail_errno( $fn );
    local $Data::Dumper::Indent;
    local $Data::Dumper::Quotekeys;
    local $Data::Dumper::Terse;
    $Data::Dumper::Indent = 1;
    $Data::Dumper::Quotekeys = 0;
    $Data::Dumper::Terse = 1;

    print FH Dumper( $structure ) or $self->_fail_errno( $fn );
    close FH                      or $self->_fail_errno( $fn );
}


sub read_perldata {
    my $self = shift;
    my ( $fn ) = @_;

    ## make it absolute to prevent @INC searching.
    $fn = File::Spec->rel2abs( $fn, $self->WorkDir )
        unless File::Spec->filename_is_absolute( $fn );

    my $v = do $fn;

    if ( ! defined $v ) {
        $self->_fail_errno( $fn ) if $!;
        $self->_fail( "$@: $fn" ) if $@;
    }
    return $v;
}


sub save_original_file {
    my $self = shift;
    my ( $path ) = @_;
    my $saved_path = $self->_get_saved_original_location( $path );

    ## Never overwrite a previously saved file.  I'd like install() to
    ## should detect these and delete them, somehow, so a reconfigured
    ## install works.  Not sure how, unless we store a state file in
    ## the workdir.
    ## Hmmm, that's not a bad plan.
    return if -e $saved_path;

    $self->_copy_file( $path, $saved_path );

    my @stats = stat $path;

    my $meta = {
        Path      => $path,
        SavedPath => $saved_path,
        mode      => $stats[0],
        uid       => $stats[4],
        gid       => $stats[5],
        atime     => $stats[8],
        mtime     => $stats[9],
    };

    $self->_store_perldata(
        $self->_get_saved_original_meta_info_location( $path ),
        $meta,
    );
}


sub chmod {
    my $self = shift;
    my ( $perms, $path, $options ) = @_;
    $options ||= {};

    my $show_perms = $perms =~ /^[0-9]+\z/
        ? _as_octal( $perms )
        : $perms;

    $self->_log_cmd(
        "chmod $show_perms $path # ",
        $self->_perms_to_letters( $perms )
    ) unless $options->{Silent};
    CORE::chmod $perms, $path
        or $self->_fail_errno( $path );
}


sub escape_command_line_parameter {
    my $self = shift;

    for ( shift ) {
        return qq{"$_"} if m{[^\w=/\\+-]};
        return $_;
    }
}


sub getgrnam {
    my $self = shift;
    my ( $gname ) = @_;

    return CORE::getgrnam $gname
        or $self->_fail_errno( "looking up group name for $gname");
}


sub getgrgid {
    my $self = shift;
    my ( $gid ) = @_;

    return CORE::getgrgid $gid
        or $self->_fail_errno( "looking up group id for $gid");
}


sub getpwnam {
    my $self = shift;
    my ( $uname ) = @_;

    return CORE::getpwnam $uname
        or $self->_fail_errno( "looking up user $uname");
}


sub getpwuid {
    my $self = shift;
    my ( $uid ) = @_;

    return CORE::getpwuid $uid
        or $self->_fail_errno( "looking up user $uid");
}


sub chown {
    my $self = shift;
    my ( $uid, $gid, $path ) = @_;

    my $gname;
    
    if ( $gid =~ /^\d+$/ ) {
        $gname = $self->getgrnam( $gid );
    }
    else {
        $gname = $gid;
        $gid = $self->getgrgid( $gname );
    }

    my $uname;

    if ( $uid =~ /^\d+$/ ) {
        $uname = $self->getgrnam( $uid );
    }
    else {
        $uname = $uid;
        $uid = $self->getgrgid( $uname );
    }

    $self->_log_cmd( "chown $gname:$uname  # ($gid:$uid)" ) ;
    CORE::chown $uid, $gid, $path
        or $self->_fail_errno( $path );
}


sub _iso8601_localtime {
    my $self = shift;

    my @times = localtime @_;

    $times[5] += 1900;
    $times[4] += 1;

    ## TODO: add the timezone indicator
    return sprintf( "%04d/%02d/%02d-%02d:%02d:%02d", reverse @times[0..5] );
}


sub _perms_to_letters {
    my $self = shift;

    ## TODO: letterify sticky, etc.
    my @letters = reverse split //, "rwxrwxrwx";
    my @bits    = reverse split //, sprintf( "%09b", shift );
    return join "", reverse map {
        my $l = shift @letters;
        $_ ? $l : "-";
    } @bits;
}


sub _as_octal { sprintf "0%o", shift }


$ENV{PWD} = Cwd::cwd();

sub cd {
    my $self = shift;
    my $options = ref $_[-1] eq "HASH" ? pop : {};
    my $name = shift if @_ > 1;
    my ( $dir ) = @_;

    my $cmt = defined $name ? "   # ($name)" : "";

    ## We only output the result if it looks different from where
    ## we think we are.
    $self->_log_cmd( "cd $dir$cmt" )
        if $dir ne $ENV{PWD} && ! $options->{Silent};

    ## We always do the syscall in case someone else called CORE::cd.
    chdir $dir or die $self->_fail_errno( "cding to $dir$cmt" );

    $ENV{PWD} = $dir;
}


sub cwd {
    my $self = shift;
    return $ENV{PWD};
}


sub utime {
    my $self = shift;
    my $options = @_ && ref $_[-1] eq "HASH" ? pop : {};
    my ( $atime, $mtime, $path ) = @_;

    my $atime_str;
    if ( $atime =~ /^\d+\z/ ) {
        $atime_str = $self->_iso8601_localtime( $atime );
    }
    else {
        $atime_str = $atime;
    }

    my $mtime_str;
    if ( $mtime =~ /^\d+\z/ ) {
        $mtime_str = $self->_iso8601_localtime( $mtime );
    }
    else {
        $mtime_str = $mtime;
    }

    $self->_log_cmd( "touch -t $atime_str --time=access $path" )
        unless $options->{Silent};
    $self->_log_cmd( "touch -t $mtime_str $path" ) 
        unless $options->{Silent};
    CORE::utime $atime, $mtime, $path
        or $self->_fail_errno( $path );
}


sub touch {
    my $self = shift;
    my $options = @_ && ref $_[-1] eq "HASH" ? pop : {};
    my ( $atime, $mtime ) = @_ > 1 ? ( shift, shift ) : ();
    my ( $path ) = @_;

    Carp::confess "undefined $path" unless defined $path;
    Carp::confess "empty $path" unless length $path;

    unless ( -e $path ) {
        $self->mkparentpath( $path );
        open F, ">$path" or $self->_fail_errno( "creating '$path'" );
        close F          or $self->_fail_errno( "closing '$path' after creation" );
        return unless defined $atime || defined $mtime;
    }

    my $t = time unless defined $atime && defined $mtime;
    $atime = $t unless defined $atime;
    $mtime = $t unless defined $mtime;

    return $self->utime( $atime, $mtime, $path, $options );
}


sub mtime {
    my $self = shift;
    my ( $path ) = @_;

    Carp::confess "undefined \$path!" unless defined $path;

    my $t = (stat $path)[9];
    return defined $t ? $t : -1;
}


sub restore_file {
    my $self = shift;
    my ( $path ) = @_;

    my $saved_path = $self->_get_saved_original_location( $path );

    $self->_fail( "saved copy of original not found: $saved_path" )
        unless -e $saved_path;

    my $meta = $self->read_perldata(
        $self->_get_saved_original_meta_info_location( $path )
    );

    $self->_copy_file( $saved_path, $path );

    $self->utime( $meta->{atime}, $meta->{mtime}, $path );
    $self->chmod( $meta->{mode}, $path );
    $self->chown( $meta->{gid}, $meta->{uid}, $path );
}


sub rm {
    my $self = shift;
    my $options = @_ && ref $_[-1] eq "HASH" ? pop : {};
    my ( $path ) = @_;

    $self->_log_cmd( "rm $path" ) unless $options->{Silent};
    unlink $path or $self->_fail_errno( "deleting $path" );
}


sub rm_if_present {
    my $self = shift;
    my ( $path ) = @_;

    $self->rm( @_ ) if -e $path;
}


sub rmtree {
    my $self = shift;
    my ( $path ) = @_;

    if ( -e $path ) {
        $self->_log_cmd( "rm -rf $path" );
        File::Path::rmtree( [$path] );
    }
}


## not doing a simple move because I think the "is the target a file or
## a dir" DWIMmery is too vague.  Spelling out "move" to hint at 
## the distinction and to allow for a later mv() when I get less uptight.
sub move_dir {
    my $self = shift;
    my ( $from, $to ) = @_;

    ## TODO: Don't assume from and to are on the same volume.
    rename $from, $to or $self->_fail_errno( "moving '$from' to '$to'" );
}


sub mkpath {
    my $self = shift;

    my ( $path, $perms ) = @_;

    unless ( -d $path ) {
        $self->_log_cmd( "mkdir -p $path" );
        $perms = 0755 unless defined $perms;
        $self->_log( " WARNING: Creating $path with 0 perms!" )
            unless $perms;
        my $show_perms = $perms =~ /^[\d+]/
            ? _as_octal( $perms )
            : $perms;
        $self->_log_cmd(
            "chmod $show_perms $path    # ", $self->_perms_to_letters( $perms )
        );

        File::Path::mkpath( [$path], $self->_tracing > 2, $perms );
    }
}


sub mkparentpath {
    my $self = shift;
    my ( $path, $perms ) = @_;
    my ( undef, $dir ) = File::Basename::fileparse( $path );
    $self->mkpath( $dir );
}


sub read_file {
    my $self = shift;
    my ( $path ) = @_;
    my $body;

    open  F, "<$path"        or $self->_fail_errno( "opening $path for read" );
    read  F, $body, -s $path or $self->_fail_errno( "reading from $path" );
    close F                  or $self->_fail_errno( "closing $path" );

    return $body;
}


sub write_file {
    my $self = shift;
    ## Leave @body in @_ to minimize copying
    my ( $path, $perms ) = ( shift, shift );
    my $options = ref $_[-1] ? pop : {};

    $perms ||= 0700;
    ## TODO: autobinmode by looking at $body

    $self->_log_cmd( "install $path" ) unless $options->{Silent};

    open  F, ">$path" or $self->_fail_errno( "opening $path for write" );
    print F @_        or $self->_fail_errno( "writing to $path" );
    close F           or $self->_fail_errno( "closing $path" );
    
    $self->chmod( $perms, $path, $options );
}


sub read_dir {
    my $self = shift;
    my ( $path ) = @_;

    opendir D, $path or $self->_fail_errno( "opening $path for read" );

    ## TODO: Other OSDrivers will need different filters here, but DOS-like
    ## and Unix-like OSs should be fine.  Hmmm, File::Spec might be a good
    ## place for such a filter.
    my @d = grep ! /^\.\.?(?!\n)\Z/, readdir D;
    closedir D or $self->_fail_errno( "opening $path for read" );

    ## Having to cd there sux, but it can't be helped since there's
    ## no way to tell whether the thing is a file or a directory and
    ## File::Spec needs us to call the correct method.
    my $cwd = $self->cwd;
    $self->cd( $path, { Silent => 1 } );
    
    my @entries = map
        -d $_
            ? File::Spec->catdir( $path, $_ )
            : File::Spec->catfile( $path, $_ ),
        @d;

    $self->cd( $cwd, { Silent => 1 } );
    return @entries;
}


sub append_to_file {
    my $self = shift;
    my $path = shift;

    $self->_log_cmd( "install >>$path" );

    ## TODO: autobinmode by looking at the file
    open  F, ">>$path" or $self->_fail_errno( "opening $path for append" );
    print F @_         or $self->_fail_errno( "writing to $path" );
    close F            or $self->_fail_errno( "closing $path" );
}

=item run

    $foo->_os->run( @cmd );
    $foo->_os->run( @cmd, \%options );

Runs a command using system().  Will fail if a problem is found or
(by default) a non-zero return value is returned from the command.

Options may be:

    Silent       => 1,   ## Turn off logging of command to STDERR
    ReturnResult => 1,   ## Return the result value ($?)

=cut

sub run {
    my $self = shift;
    my $options = @_ && ref $_[-1] ? pop : {};
    my ( @cmd ) = @_;

    @cmd = map {
        my $c = $_;

        $c = $c->( $self->_caller || $self ) if ref $c eq "CODE";

          ref $c eq "ARRAY"   ? @$c
        : ref $c eq "HASH"    ? map "$_=$c->{$_}", sort keys %$c
                              : $c
    } @cmd;

    my $cmd = join " ", @cmd;
    $self->_log_cmd( $cmd ) unless $options->{Silent};
    my $r = system @cmd;

    unless ( $options->{ReturnResult} ) {
        $self->_fail( ": couldn't run `$cmd`")
            if ! defined $r || $r < 0;

        $self->_fail( ": nonzero return from `$cmd`: ", $r >> 8 )
            if $r;
    }

    return $r ? $r >> 8 : $r
}


sub run_and_capture_stdout {
    my $self = shift;
    my ( @cmd ) = @_;

    my $cmd = join " ", map defined $_ ? $_ : "<<UNDEF>>", @cmd;

    $self->_fail( "Undefined value in $cmd" )
        if grep ! defined, @cmd;

    $self->_log_cmd( $cmd );
    my $stdout = `$cmd`;
    $self->_fail_errno( ": running `$cmd`")
        unless defined $stdout;
    $self->_fail( ": `$cmd` returned ", $? >> 8 )
        if $?;
    return $stdout;
}




=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/OSDriver.pm"} = "inlined" if length "MBall/OSDriver.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall::Object"
package MBall::Object;

use strict;
use Carp ();

## Class vars.

sub new {
    my $proto = shift;
    my $class = ref $proto || $proto;

    my $self = bless {}, $class;

    %$self = (
        ! ( @_ == 1 && ref $_[0] ) ? @_
            : ref $_[0] eq "HASH"  ? %{shift()}
            : ref $_[0] eq "ARRAY" ? ( Data => shift )
            : $self->_fail( "illegal initializer $_[0]")
    );

    return $self;
}

sub _set_defaults {
    my $self = shift;
    my $defaults = @_ == 1 && ref $_[0] ? shift : { @_ };

    for ( keys %$defaults ) {
        next if defined $self->{$_};
        $self->{$_} = $defaults->{$_};
    }
}

{
    my $tracing = 1;

    sub _tracing {
        warn "_tracing takes no args, perhaps call _set_tracing?\n" if @_>1;
        return $tracing;
    }

    sub _set_tracing { shift; $tracing = shift }
}

sub _os {
    my $self = shift;

    my $os = $self->{_OSDriver};
    $os = $self->{_Container}->_os
        if !$os && $self->{_Container};
    $self->_fail( "no OSDriver") unless $os;
    Carp::confess "$os not an OSDriver"
        unless UNIVERSAL::isa( $os, "MBall::OSDriver" );
    $os->_set_caller( $self );
    return $os;
}


sub _field_display_position {
    my $self = shift;
    return 2_000_000 if $_[0] eq "Actions";
    return 2_000_000 if $_[0] eq "Body";
    return 1_000_000;
} 


sub _sort_field_names_for_display {
    my $self = shift;

    my %seen;
    my %cache;

    return sort {
        ( $cache{$a} ||= $self->_field_display_position( $a ) )
        <=>
        ( $cache{$b} ||= $self->_field_display_position( $b ) )
        ||
        lc $a cmp lc $b
    } grep ! $seen{$_}++, @_;
}


sub _field_names {
    my $self = shift;
    return $self->_sort_field_names_for_display(
        grep index( $_, "_" ), ( @_, keys %$self )
    );
}


sub _is_list_field { my $self = shift; return 0; }

sub _all_field_names {
    my $self = shift;
    return $self->_sort_field_names_for_display( @_, keys %$self )
}


sub _id {
    my $self = shift;
    my @id;

    push @id, $self->{Name}
        if     exists  $self->{Name}
            && defined $self->{Name}
            && length  $self->{Name};

    if ( $self->_tracing > 1 || ! @id ) {
        unshift @id, ref $self;
    }

    return join " ", @id;
}


sub _fail_msg {
    my $self = shift;
    my $options = @_ && ref $_[-1] eq "HASH" ? pop : {};
#    my $dump = $self->dump;
#    $dump =~ s/^/    /gm;

    my $id = $self->_id;
    my $msg = join "", map
        ref $_ 
            ? UNIVERSAL::can( $_, "description_as_string" )
                ? $_->description_as_string
                : $self->_dump( "", $_, { ExpandMacros => 0 } )
            : $_,
        @_;
    chomp $msg;
    return "$msg\n" if 0 == index $msg, $id;

    ## Assume defaultChar is never "" or "0" (zero).
    $id .= ( $options->{DefaultChar} || ":" ) . " " if $msg =~ /^[\w({[]/;

    return "$id$msg\n";#, $dump, 
}


sub _fail {
    my $self = shift;
    print STDERR $self->_fail_msg( "EXCEPTION: ", @_ )
        if $self->_tracing >= 9;

    $self->_tracing >= 2
        ? Carp::confess( $self->_fail_msg( @_ ) )
        : die(           $self->_fail_msg( @_ ) );
}


sub _confess {
    my $self = shift;
    Carp::confess( $self->_fail_msg( @_ ) );
}


sub _log_cmd { shift->_log( "\$ ", @_ ) }


sub _log {
    my $self = shift;
    my $indent_level = 0;

    if ( $self->_tracing > 1 ) {
        my $stack_depth = 0;
        my @foo;

        while ( @foo = caller( $stack_depth ) ) {
            ++$stack_depth;
            ++$indent_level
        }
    }

    my $msg = $self->_fail_msg(  @_, { DefaultChar => "##" } );

    $self->_tracing >= 5
        ? Carp::cluck $msg
        : print STDERR " " x ( $indent_level - 2 ), $msg;
}

## TODO: make expand_macros recursive and let it handle CODE, HASH, and ARRAY
## parms.
## TODO: make expand_macros check for use of a list macro inside a non-list
## macro.
sub _expand_macros {
    my $self = shift;
    my ( $name, $value, $options ) = @_;

    return "<DUPLICATE REFERENCE $value>"
        if ref $value && $self->{_ExpandSeen}->{int $value}++;

    $self->_fail( "$name is declared but not defined" )
        unless defined $value;

    my $type = ref $value;

    if ( $type eq "CODE" ) {
        return ""
            if defined $options->{ExecuteSubs} && ! $options->{ExecuteSubs};

        $value = $value->( $self, $name, $options );
        $self->_fail( "$name->(...) returned an undefined value" )
            unless defined $value;
        $type = ref $value;
    }

    return $value
        if defined $options->{ExpandMacros} && ! $options->{ExpandMacros};

    ## Don't bother doing this check if the user requests no macro expansion,
    ## that way references can be pulled out untouched in to scalars
    ## by turning of expansion.
    my $is_list_macro = $self->_is_list_field( $name );
    $self->_confess( "list field $name requested in scalar context" )
        if ! $options->{_DontCheckContext}
            && $options->{Flatten}
            && $is_list_macro
            && defined wantarray
            && ! wantarray;

    if ( ! $type && $is_list_macro ) {
        ## TODO: Allow list sep option, comma would be handy, and
        ## colon and semicolon would be handy for PATH variables.
        $value = [ split /\s+/, $value ];
        shift @$value while @$value && ! length $value->[0];
        if ( @$value > 1 ) {
            $type = "ARRAY";
        }
        else {
            $value = @$value ? $value->[0] : ""
        }
    }

    if ( ! $type ) {
        my %seen;

        my @errors;
        my %macros;
        {
            ## Do this destructively so we can look for extra macro tags
            ( my $v = $value ) =~ s{<%(.*?)%>}{
                $macros{$1} = undef;
                ## Maintain position and also make sure that "<<%Foo%>%"
                ## doesn't cause an error.
                " " x ( 4 + length $1 );
            }ge;
            if ( 0 <= ( my $pos = index $v, "<%" ) ) {
                push @errors, "unmatched '<%' at character ", $pos + 1;
            }
            if ( 0 <= ( my $pos = index $v, "%>" ) ) {
                push @errors, "unmatched '%>' at character ", $pos + 1;
            }
        }


        my %values;

        my $macro_options = {
            $is_list_macro ? () : ( Flatten => 1 ),
            ExecuteSubs => $options->{ExecuteSubs},
        };

        for my $macro ( sort keys %macros ) {
#            push @errors, "List macro <%$macro%> used in non-list macro"
#                unless $is_list_macro || ! $self->_is_list_field( $macro );

            unless (
                eval { $values{$macro} = [ $self->$macro( $macro_options ) ] }
            ) {
                push @errors,
                    $@ =~ /Can't locate object method/
                        ? "unknown macro <%$macro%>"
                        : $@;
                next;
            }
            elsif( grep !defined, @{$values{$macro}} ) {
                push @errors, "undefined value for <%$macro%>";
                next;
            }

            $values{$macro} = [ join " ", @{$values{$macro}} ]
                unless $is_list_macro;
        }

        ## This next bit of loopiness expands the string in to 0 or more
        ## strings by interpolating each value of each macro, resulting
        ## in something like a dot product; two macros, one with two
        ## values and one with three, will result in 6 strings.
        my @results = ( $value );

        for my $macro ( keys %values ) {
            my @strings = @results;
            @results = ();
            for my $string ( @strings ) {
                for my $value ( @{$values{$macro}} ) {
                    $self->_log( "$macro => $value in '$string'" )
                        if $self->_tracing > 10;
                    push @results, $string;
                    $results[-1] =~ s/<%\s*$macro\s*%>/$value/gi;
                }
            }
        }

        if ( @errors ) {
            $self->_fail(
                "errors encountered in $name => '$value':\n",
                map { s/^/    /mg; chomp; "$_\n" } @errors
            ) if @errors > 1;

            $self->_fail( $errors[0], " in $name => '$value'" );
        }

        $self->_log( "Macro expansion failed for $name => '$value'" )
            if ! @results && $self->_tracing;

        return wantarray ? @results : join " ", @results;
    }
    ## TODO: may need an expansion context passed in to expand_macros;
    ## or maybe we should let a subobject expand macros if it can.
    elsif ( UNIVERSAL::isa( $value, "HASH" ) ) {
        local $options->{_DontCheckContext} = 1;
        $value = {
            map {
                index( $_, "_" ) == 0
                    ? () # starts with "_"
                    : ( $_ =>
                       scalar $self->expand_macros( $_, $value->{$_}, $options )
                    );
            } keys %$value
        };

        return $value if defined $options->{Flatten} && ! $options->{Flatten};

        $value = [ map "$_=$value->{$_}", sort keys %$value ];

        return wantarray ? @$value : join " ", @$value;
    }
    elsif ( UNIVERSAL::isa( $value, "ARRAY" ) ) {
        $value = [ map $self->expand_macros( $name, $_, $options ), @$value ];

        return $value if defined $options->{Flatten} && ! $options->{Flatten};

        return wantarray ? @$value : join " ", @$value;
    }

    return "<Can't expand $value>";
}


sub expand_macros {
    my $self = shift;

    ## There should be no loops, but just in case...
    $self->{_ExpandSeen} = ();
    $self->_expand_macros( @_ );
}


sub AUTOLOAD {
    my $self = shift;

    use vars qw( $AUTOLOAD );
    ## TODO: unicodise this match.
    if ( $AUTOLOAD =~ /.*::([A-Z]\w*)$/ ) {
        ## Looks like StudlyCaps
        my $sub = $self->can( "_get" );
        if ( $sub ) {
            unshift @_, $self, $1;
            goto &$sub;
        }
    }

    Carp::confess "Can't locate object method $AUTOLOAD for class ",
        ref $self || $self, "\n"
        if $AUTOLOAD =~ /[a-z0-9]/;
}

sub DESTROY {};


## This is used to implement data "inheritance" from the Installer
## a CODE ref should C<return []> to return an empty list and
## C<return \@v> to prevent macro expansion.  Calling $_[0]->inherit( $name )
## provides access to data inheritence.
sub inherit { 
    my $self = shift;

    return unless exists $self->{_Container} && $self->{_Container};
    Carp::confess if $self == $self->{_Container};

    my $sub = $self->{_Container}->can( "_get" );
    return unless $sub;

    unshift @_, $self->{_Container};
    goto &$sub;
}


sub _flatten_ISA {
    my $class = shift;
    no strict "refs";

    my @isa = do {
        no strict "refs";
        @{"${class}::ISA"};
    };
    
    return ( $class, map _flatten_ISA( $_ ), @isa );
}


sub _get_class_macro {
    my $self = shift;
    my ( $macro_context, $name, $options ) = @_;

    my $installer = $self;
    $installer = $installer->{_Container}
        while exists $installer->{_Container} && $installer->{_Container};

    my $classes = $installer->{Classes};

    return unless $classes;

    for ( _flatten_ISA ref $self ) {
        s/MBall:://;
        return $macro_context->expand_macros(
            $name, $classes->{$_}->{$name}, $options
        ) if exists $classes->{$_} && exists $classes->{$_}->{$name};
    }

    return ();
}


sub _get_with_inheritance {
    my $self = shift;
    my ( $macro_context, $name, $options ) = @_;

    return $macro_context->expand_macros( $name, $self->{$name}, $options )
        if exists $self->{$name};

    my @v = $self->_get_class_macro( @_ );
    return @v if @v;

    return unless exists $self->{_Container} && $self->{_Container};
    Carp::confess if $self == $self->{_Container};

    my $sub = $self->{_Container}->can( "_get_with_inheritance" );
    unshift @_, $self->{_Container};
    goto &$sub;
}


use vars qw( $_in_get );   ## eeewwww.
sub _get {
    my $self = shift;
    my ( $name, $options ) = @_;

    $options ||= {};

    $self->_fail(
        $name,
        " is a list macro but was requested in a scalar context at ",
        (caller)[1],
        ", line ", 
        (caller)[2],
    )
        if $self->_is_list_field( $name ) && wantarray && ! wantarray;

    $self->_log( "getting $name at ",
        (caller)[1],
        ", line ", 
        (caller)[2],
    ) if $self->_tracing >= 11;

    my @v = do {
        local $_in_get = 1;
        $self->_get_with_inheritance( $self, $name, $options );
    };

    $self->_log(
        "got $name => (",
        join( ", ", map defined $_ ? "'$_'" : "undef", @v ),
        ")"
    ) if $self->_tracing >= 11 || ( $self->_tracing >= 10 && ! $_in_get );

    return wantarray ? @v : $v[0];
}


sub _dump {
    my $self = shift;
    my ( $name, $v, $options ) = @_;

    my $comma = $options->{_Comma} || "";

    return "undef$comma" unless defined $v;

    my $dont_expand_more;
    unless ( ref $v ) {
        local $options->{Flatten} = 0;
        $v = ( $options->{_MacroContext} || $self )->expand_macros(
            $name,
            $v,
            $options
        );
        unless ( ref $v ) {
            if ( 
                0 <= index( $v, "\n" )
                && rindex( $v, "\n" ) eq length( $v ) -1
            ) {
                my $TERM = $name;
#                $TERM = $options->{_NextLabel} unless defined $TERM;
                $TERM = "STRING" unless defined $TERM;
                $TERM = "\U${TERM}_END";
                chomp $comma;
                $TERM =~ s/\W+//;
                while ( 0 <= index $v, $TERM ) {
                    $TERM =~ s{$|_\d+$}{
                        "_" . ( ( $1 || -1 ) + 1 );
                    }e
                }
                return "<<'$TERM'$comma\n$v$TERM\n";
            }
            $v =~ s/([\\'])/\\$1/g;
            return "'$v'$comma";
        }

        ## We just expanded these if they needed expansion.  We want to
        ## dump the structure without reexpanding, in case there was
        ## a "<%" in a macro value.  Plus, why spend the CPU time?
        $dont_expand_more = 1;
    }

    local $options->{ExpandMacros} = 0 if $dont_expand_more;

    return "<LOOP REFERENCE>$comma" if $self->{_DumpSeen}->{int $v}++;

    local $options->{Indent} = "  " unless defined $options->{Indent};
    my $open_indent = $options->{_OpenIndent} || "";
    my $cur_indent  = $options->{_CurIndent} || "";
    my $next_indent = $cur_indent . $options->{Indent};
    local $options->{_CurIndent} = $next_indent;
#    local $options->{_Label} = $options->{_NextLabel};
#    local $options->{_NextLabel} = undef;

    local $options->{_MacroContext} = $v
        if UNIVERSAL::isa( $v, "MBall::Object" );

    if ( UNIVERSAL::isa( $v, "HASH" ) ) {
        my @keys = UNIVERSAL::can( $v, "_field_names" )
            ? $v->_field_names
            : sort keys %$v;

        @keys = grep index( $_, "_" ), @keys
            unless $options->{ShowPrivate};

        ## The _Comma hack lets us do <<'FOO', correctly,

        local $options->{_Comma} = @keys < 1 ? "" : ",\n";

        my @values = map {
#            local $options->{_Label} = $_;
#            local $options->{_NextLabel} = $_;
            my $elt_v;

            if ( UNIVERSAL::can( $v, "_get" ) ) {
                my @v = $v->_get( $_, $options );
                $elt_v =
                      @v == 0                ? "undef$comma"
                    : ( @v == 1 && ! ref $v[0] ) ? "'$v[0]'$comma"
                    :                        $self->_dump( $_=>\@v, $options );
            }
            else {
                $elt_v = $self->_dump( $_ => $v->{$_}, $options );
            }
            $elt_v = undef if !index $elt_v, "undef";

            ## undef the key if the value is undefined.
            $_ = undef unless defined $elt_v;
            defined $elt_v ? $elt_v : ();
        } @keys;

        @keys = grep defined, @keys;

        my $max_key_width         = 0;
        my $next_to_max_key_width = 0;
        my %keys;
        for my $key ( @keys ) {
            my $k = $key;
            $k =~ s/([\\'])/\\$1/g;
            $k = "'$k'" if $k =~ /\W/;
            $keys{$key} = $k;
            if ( length $k >= $max_key_width ) {
                $next_to_max_key_width = $max_key_width;
                $max_key_width = length $k;
            }
            elsif ( length $k >= $next_to_max_key_width ) {
                $next_to_max_key_width = length $k;
            }
        }

        my $key_width = $max_key_width > ( $next_to_max_key_width + 2 ) * 1.10
            ? $next_to_max_key_width
            : $max_key_width;

        my $fmt = "%-${key_width}s => %s";
        $fmt = "$next_indent$fmt";
        my $fmt2 = "$next_indent%s => %s";

        return join( "",
            $open_indent,
            "{\n",
            map( sprintf( $fmt, $keys{$_}, shift @values ), @keys ),
            $cur_indent,
            "}",
            $comma,
        );
    }

    if ( UNIVERSAL::isa( $v, "ARRAY" ) ) {
        return "[]$comma" unless @$v;

#        local $options->{_NextLabel} = undef;
        local $options->{_OpenIndent} = $next_indent;
        local $options->{_Comma} = ",\n";

        return join( "",
            $open_indent,
            "[\n",
            map(
                $next_indent . $self->_dump( $name => $_, $options ),
                @$v
            ),
            $cur_indent,
            "]",
            $comma,
        );
    }

    return "$v$comma";
}


sub description_as_string {
    my $self = shift;
    my ( $options ) = @_;

    local $self->{_DumpSeen} = {};
    local $options->{_Comma} = ",\n";
    local $options->{ExpandMacros} = 0 unless defined $options->{ExpandMacros};
    return $self->_dump( ref $self => $self, $options );
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall/Object.pm"} = "inlined" if length "MBall/Object.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
BEGIN {
no strict;
#line 1 "MBall"
package MBall;

@ISA = qw( MBall::Object );
$VERSION = 0.02;

use strict;

use Carp;

use MBall::Object;
use MBall::OSDriver;
use MBall::Package;

sub new {
    my $self = bless {}, ref $_[0] ? ref shift : shift;

    %$self = %{ shift || {} };  ## TODO: a deep copy.

    $self->reset_db unless keys %$self;

    $self->{_OSDriver} = MBall::OSDriver->new( _Container => $self )
        unless $self->{_OSDriver};

    return $self;
}


sub _init_db {
    my $self = shift;

    ## Sort by length so assumed base classes come first
    my $classes = delete $self->{Classes};
    for ( sort { length $a <=> length $b } keys %$classes ) {
        $self->add_class( $_, delete $classes->{$_} );
    }

    if ( my $pkgs = delete $self->{Packages} ) {
        $self->add_package( $_ ) for @$pkgs;
    }

    $self->validate;
}


sub reset_db {
    my $self = shift;

    $self->_log( "resetting db" ) if $self->_tracing;

    delete $self->{$_} for grep /^[^_]/, keys %$self;
    require MBall::DB;
    $self->load_db( defaults => MBall::DB->default_db );

    ## Don't include the defaults in the list of loaded DBs.
    $self->{_DBsLoaded} = [];
}


sub load_db {
    my $self = shift;
    my ( $name, $db ) = @_;

    push @{$self->{_DBsLoaded}}, [ $name, $db ];

    $self->_log( "adding $name to db" ) if $self->_tracing;

    if ( ref $db eq "HASH" ) {
        ## Fall through
    }
    elsif ( ref $db eq "SCALAR" ) {
        ## TODO: move this to a clean lexical env.
        $db = eval $db;
        $self->_fail( $@ ) if $@;
        $self->_fail( "$name DB returned ",
            ref $db
                ? "a " . ref $db
                : "'db'",
            "not a HASH ref"
        ) unless ref $db eq "HASH";
    }
    elsif ( ref $db ) {
        $self->_fail(
            "load_db can only load from a file, HASH, or SCALAR, not ",
            ref $db
        );
    }
    else {
        my $return = do $db unless ref $db;
        $self->_fail( $@ ) if $@;
        $self->_fail( $! ) unless defined $return;
        $self->_fail( "$name DB in $db returned ",
            ref $return
                ? "a " . ref $return
                : "'return'",
            "not a HASH ref"
        ) unless ref $return eq "HASH";
        $db = $return;
    }

    for ( keys %$db ) {
        $self->{$_} = $db->{$_};
    }

    $self->_init_db
}


sub add_class {
    my $self = shift;
    my ( $name, $def ) = @_;

    $self->_log( "adding class $name" ) if $self->_tracing;

    $self->{Classes}->{$name} = $def;

    my $isa;
    $isa = $def->{ISA}
        if exists $def->{ISA};

    unless ( defined $isa ) {
        $isa = $name;
        $isa = undef unless $isa =~ s/::[^:]+$//;
    }

    unless ( eval "require MBall::$name" ) {
        die $@ unless $@ =~ /Can't locate/;
    }

    if ( defined $isa ) {
        no strict "refs";
        my @isa = 
            map "MBall::$_",
                ref $isa
                    ? @$isa
                    : split /\s+/, $isa;
        $self->add_class( $_ )
            for grep ! exists $self->{Classes}->{$name}, @isa;

        @{"MBall::${name}::ISA"} = @isa;
    }
}


sub add_package {
    my $self = shift;

    confess "undef passed" unless defined $_[0];

    my $pkg = UNIVERSAL::isa( $_[0], "MBall::Package" )
        ? shift
        : MBall::Package->new_subclass( 
            %{shift()},
            _Container => $self,
        );

    my $name = $pkg->Name;

    $self->_log( "adding package $name" ) if $self->_tracing;

    die "Duplicate package name: $name\n"
        if eval { $self->_search_packages( $name ) };

    push @{$self->{Packages}}, $pkg;
}


sub _search_packages {
    my $self = shift;
    my ( $name ) = @_;

    for ( @{$self->{Packages}} ) {
        return $_ if $_->{Name} eq $name;
    }

    return;
}


sub get_package {
    my $self = shift;
    my ( $name, $fallback ) = @_;

    my $pkg = $self->_search_packages( $name );

    if ( ! $pkg && $fallback ) {
        my $prereq = $fallback->BuildPrereqPackage(
            {
                Flatten       => 0,
                Name          => $name,
            }
        );
        $self->add_package( $prereq )
            if defined $prereq;
        $pkg = $self->_search_packages( $name );
    }

    return $pkg if $pkg;

    ## Let's see if any of the finders know anything about it
    my @finders = $self->_os->find_plugins( "MBall::Finder" );

    my @pkgs;
    for ( @finders ) {
        $self->{_Finders}->{$_} ||= $_->new( _Container => $self );
        $pkg = eval {
            MBall::Package->new_subclass(
                $self->{_Finders}->{$_}->query_package( $name ),
                _Container => $self,
                AutoGenerated => 1,
            )
        };
        if ( $pkg ) {
            $self->add_package( $pkg );
            return $pkg;
        }
    }

    die "Unknown package name '$name'\n";
}


sub dump_packages {
    my $self = shift;

    local $self->{_DumpSeen} = {};
    $self->_dump( Packages => $self->{Packages} );
}


sub package_names {
    my $self = shift;

    return map $_->{Name}, @{$self->{Packages}};
}


sub validate {
    my $self = shift;

    my %options = @_;
    $options{Fatal} = 1 unless defined $options{Fatal};

    my @errors;
    for my $pkg ( @{$self->{Packages}} ) {
        for ( $pkg->Requires ) {
            next if eval { $self->get_package( $_, $pkg ); 1 };
            warn $@;
            push @errors, $pkg->_fail_msg( " requires unknown package '$_'" );
        }

        for ( $pkg->ExternalComponents ) {
            next if eval { $self->get_package( $_, $pkg ); 1 };
            push @errors, $pkg->_fail_msg(
                " requires unknown external component package '$_'"
            );
        }
        my $ok = eval { $pkg->validate; 1 };
        push @errors, $@ unless $ok;
    }

    die @errors if @errors && $options{Fatal};

    return @errors;
}


sub _build_in_memory_package_list {
    my $self = shift;

        ## Sigh.  This is to make sure that DetectedRequires end up
        ## as Packages.
## BUG TRIGGERED BY:
##     $_->install
##         for @{$self->{Packages}};
    $_->check_make
          for @{$self->{Packages}};
}


sub _to_module_name($) {
    my $name = shift;
    $name =~ s{\..*}{};
    $name =~ s{/}{::}g;
    return $name;
}


sub write_installer {
    my $self = shift;

    my $cwd = $self->_os->cwd;

    die "No packages loaded\n"
        unless $self->{Packages} && @{$self->{Packages}};

    my @default_packages = split /\s+/, $self->DefaultPackageNames;

    my $packages_db_code = do {

        $self->_build_in_memory_package_list;
    
        require Data::Dumper;

        local $Data::Dumper::Indent    = 1;
        local $Data::Dumper::Terse     = 1;
        local $Data::Dumper::Quotekeys = 0;

        "Packages => [\n" . join(
            ",\n",
            map {
                my $def = Data::Dumper::Dumper( $_->definition_as_hash );
                chomp $def;
                $def;
            } @{$self->{Packages}}
        ) . "] # End Packages\n";
    };

    $self->_os->cd( $cwd );

    require Devel::SteamRoller;

    ## Make sure the modules we need are in %INC.
    require MBall::Package;
    require MBall::Action;
    require MBall::DB;

    ## This is a bit crude; since we have a set of packages,
    ## we should be able to find the modules we need.
    $self->_os->find_plugins( "MBall::Package" );
    $self->_os->find_plugins( "MBall::Action" );

    my @modules = 
        sort
            grep
                m{^MBall} && ! /\b(Downloader|Finder|Shell)\b/,
                keys %INC;

    my @actions = map {
        {
            action => "install",
            pkg    => $_,
        }
    } @default_packages;

    Devel::SteamRoller::combine(
        ( map {
            my $source_lib_dir =
                substr $INC{$_}, 0, length( $INC{$_} ) - length( $_ ) - 1;
            {
                TryInline     => 1,
                Path          => $INC{$_},
                ModuleName    => _to_module_name $_,
                ModuleRelPath => $_,
                DestPath      => "lib/$_",
                SourceLibDir  => $source_lib_dir,
                TargetLibDir  => "lib",
            };
        } @modules
        ),
        {
            AsInline   => 1,
            Order      => 9_999_999_999,
            ModuleName => "install driver",
            Body       => join(
                "",
                map( "use " . _to_module_name( $_ ) . ";\n", @modules ),
                <<TOHERE,
my \$m = MBall->new;
#eval {
\$m->load_db( packages => {
$packages_db_code,
} );
#};
TOHERE
                map(
                    qq{\$m->get_package( "$_->{pkg}" )->$_->{action}();\n}, #"
                    @actions
                ),
            ),
        },
        {
            Inline     => 1,
            OutputFile => File::Spec->catfile(
                $self->WorkRoot,
                "install"
            ),
        }
    );
}


#sub create_fat_tarball {
#    my $self = shift;
#
#    $self->_build_in_memory_package_list;
#
#    my $pkg = $self->get_package( ($self->DefaultPackageNames)[0] );
#    my $redistdir = $pkg->RedistBuildDir;
#    $self->_os->rmtree( $redistdir )
#        if -e $redistdir;
#    $self->_os->mkpath( $redistdir );
#    $self->_os->mkpath( "$redistdir/dists" );
#
#    for ( @{$self->{Packages}} ) {
#        $self->_os->CopyFile;
#    }
#
#    $self->_os->rmtree( $redistdir );
#}


sub _dump_tree {
    my $self = shift;
    my ( $pkgs, $indent, $name, $comment ) = @_;

    print $indent, "+-", $name, $comment || "", "\n";

    $indent = "| $indent";

    $self->_dump_tree( $pkgs, $indent, $_ )
        for @{$pkgs->{$name}->{Requires}};
    $self->_dump_tree( $pkgs, $indent, $_, " (external)" )
        for @{$pkgs->{$name}->{ExternalComponents}};
    $self->_dump_tree( $pkgs, $indent, $_, " (detected)" )
        for @{$pkgs->{$name}->{DetectedRequires}};

}


sub dep_tree { ## Undocumented; don't rely on this staying around...
    my $self = shift;

    my %pkgs;  ## Cache of relevant fields from each package
    my %required_by;

    for ( @{$self->{Packages}} ) {
        my $name = $_->Name;
        $pkgs{$name}->{Name}               = $name;
        $pkgs{$name}->{Requires}           = [ $_->Requires           ];
        $pkgs{$name}->{ExternalComponents} = [ $_->ExternalComponents ];
        $pkgs{$name}->{DetectedRequires}   = [ $_->DetectedRequires   ];
        $required_by{$name} ||= [];
        for my $r ( @{$pkgs{$name}->{Requires}},
                    @{$pkgs{$name}->{DetectedRequires}},
                    @{$pkgs{$name}->{ExternalComponents}},
        ) {
            push @{$required_by{$r}}, $name;
        }
    }

    for ( keys %required_by ) {
        next if @{$required_by{$_}};
        $self->_dump_tree( \%pkgs, "", $_ );
    }
}


sub dep_graph { ## Undocumented; don't rely on this staying around...
    my $self = shift;

    require GraphViz;

    ## NOTE: nodesep and ranksep both require a patched GraphViz.
    my $g = GraphViz->new(
        rankdir => "LR",
        nodesep => 0.1,
        ranksep => 0.1,
    );

    my $cwd = $self->_os->cwd;

    for ( @{$self->{Packages}} ) {
        my $name = $_->Name;
        my $ag   = $_->AutoGenerated;

        ## See the CHEEZY HACK comment in Package.pm
        my $has_non_default_actions = $_->{_HasCustomActions};
#            defined $_->{Actions} && @{$_->{Actions}};

        $g->add_node(
            $name,
            fontsize    => 10,
            shape       => "polygon",
            sides       => 4,
            color       => $ag ? "brown" : "black",
            fontcolor   => $ag ? "brown" : "black",
            peripheries => $has_non_default_actions ? 2 : 1,
            height      => 0.1,
        );

        $g->add_edge( $name => $_ )
            for $_->Requires;

        $g->add_edge(
            $name => $_,
            label => "ext",
            color => "blue",
            fontcolor => "blue",
            fontsize => 10,
        ) for $_->ExternalComponents;

        $g->add_edge(
            $name => $_,
            label => "det",
            color => "brown",
            fontcolor => "brown",
            fontsize => 10,
        ) for $_->DetectedRequires;
    }

    $self->_os->cd( $cwd );

#    print $g->as_canon;
    open F, ">dependancies.png" or die $!;
    binmode F;
    print F $g->as_png;
    close F;

    system "ee dependancies.png" and die "ee failed";
}


sub call_method_on_packages {
    my $self = shift;
    my $method = shift;

    ## TODO: pull off options.

    my @pkgs = map $self->get_package( $_ ), @_;

    $_->$method() for @pkgs ;
}

1;


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{"MBall.pm"} = "inlined" if length "MBall.pm";
chdir $original_cwd or die $!;
}

#=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=#
{
no strict;
#line 1 "install driver"
use MBall;
use MBall::Action;
use MBall::Action::AppendToFile;
use MBall::Action::CODE;
use MBall::Action::CopyFiles;
use MBall::Action::DeleteDir;
use MBall::Action::DeleteFile;
use MBall::Action::RunCmd;
use MBall::Action::WriteFile;
use MBall::DB;
use MBall::OSDriver;
use MBall::Object;
use MBall::Package;
use MBall::Package::PerlModule;
my $m = MBall->new;
#eval {
$m->load_db( packages => {
Packages => [
{
  MinVersion => '2.23',
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  Requires => 'expat',
  Version => '2.27',
  DistBaseName => 'XML-Parser-2.27',
  DetectedRequires => [],
  Name => 'XML::Parser',
  Style => 'PerlModule',
  DistFileNames => 'XML-Parser-2.27.tar.gz'
},
{
  MinVersion => '1.00',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  Version => '1.12',
  DistBaseName => 'XML-XPath-1.12',
  Name => 'XML::XPath',
  DetectedRequires => 'XML::Parser',
  Style => 'PerlModule',
  DistFileNames => 'XML-XPath-1.12.tar.gz'
},
{
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  Requires => 'libsablot',
  DistBaseName => 'XML-Sablotron-0.90',
  DetectedRequires => [],
  Name => 'XML::Sablotron',
  Style => 'PerlModule',
  ConfigParms => {
    PREFIX => '<%Prefix%>',
    SABLOTINCPATH => '<%Prefix%>/include',
    INSTALLSITEARCH => '<%Prefix%>/lib',
    SABLOTLIBPATH => '<%Prefix%>/lib',
    INSTALLSITELIB => '<%Prefix%>/lib'
  },
  MinVersion => '0.40',
  Version => '0.9',
  DistFileNames => 'XML-Sablotron-0.90.tar.gz'
},
{
  InstalledVersionQuery => 'perl -le \'print "<%Version%>" if -e "<%Prefix%>/lib/libsablot.so"\'',
  URIs => 'http://download-2.gingerall.cz/download/sablot/<%DistFiles%>',
  Requires => 'expat',
  Version => '0.90',
  DistBaseName => 'Sablot-0.90',
  DetectedRequires => [],
  Name => 'libsablot',
  DistFileNames => [
    'Sablot-0.90.tar.gz',
    'Sablot-0.90.tgz'
  ],
  DistName => 'Sablot'
},
{
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    },
    {
      Comment => 'Fool Makefile.PL into being well behaved',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/(RUNNING_UNDER_CPAN\\s*=\\s*)0/${1}1/m; s/(unless\\s*\\S)(\\s*have_module)/${1}0&&$2/; s/^(foreach\\s+my\\s*\\$k\\s*)/my %prereq_pm = %{delete \\$config{PREREQ_PM} || {}};\\n$1/m unless /prereq_pm/; s/^(\\s*)(%config,)/$1$2\\n${1}PREREQ_PM => \\\\%prereq_pm,/m;\' Makefile.PL'
    }
  ],
  Requires => [
    'mod_perl',
    'Apache::Filter'
  ],
  DistBaseName => 'AxKit-1.51',
  Name => 'AxKit',
  DetectedRequires => [
    'XML::LibXML',
    'Digest::MD5',
    'HTTP::GHTTP',
    'Error',
    'XML::LibXSLT',
    'XML::Sablotron',
    'XML::Parser',
    'XML::XPath',
    'Apache::Request',
    'Compress::Zlib'
  ],
  Style => 'PerlModule',
  MinVersion => '1.4',
  Version => '1.51',
  DistFileNames => 'AxKit-1.51.tar.gz'
},
{
  URIs => 'http://httpd.apache.org/dist/httpd/<%DistFileNames%>',
  Requires => [],
  Version => '1.3.23',
  DistBaseName => 'apache_1.3.23',
  DetectedRequires => [],
  Name => 'apache',
  DistFileNames => [
    'apache_1.3.23.tar.gz',
    'apache_1.3.23.tgz'
  ]
},
{
  InstalledVersionQuery => 'perl -le \'print "<%Version%>" if -e "<%Prefix%>/lib/libexpat.so"\'',
  Requires => [],
  DistBaseName => 'expat-1.95.2',
  DetectedRequires => [],
  Name => 'expat',
  URIs => 'http://prdownloads.sourceforge.net/expat/expat-1.95.2.tar.gz',
  Version => '1.95.2',
  DistFileNames => [
    'expat-1.95.2.tar.gz',
    'expat-1.95.2.tgz'
  ]
},
{
  MinVersion => '1.00',
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  Requires => 'libghttp',
  Version => '1.07',
  DistBaseName => 'HTTP-GHTTP-1.07',
  DetectedRequires => [],
  Name => 'HTTP::GHTTP',
  DistFileNames => 'HTTP-GHTTP-1.07.tar.gz',
  Style => 'PerlModule'
},
{
  MinVersion => '0.31_03',
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  Requires => 'mod_perl',
  Version => '1.0',
  DistBaseName => 'libapreq-1.0',
  DetectedRequires => [],
  Name => 'Apache::Request',
  DistFileNames => 'libapreq-1.0.tar.gz',
  Style => 'PerlModule'
},
{
  InstalledVersionQuery => 'perl -ne \'print if s/^MODULE_VERSION.*-(\\d.*\\d).*/$1/\' <%Prefix%>/lib/ghttpConf.sh',
  Requires => [],
  DistBaseName => 'libghttp-1.0.9',
  DetectedRequires => [],
  Name => 'libghttp',
  Style => 'GNOME',
  Version => '1.0.9',
  DistFileNames => [
    'libghttp-1.0.9.tar.gz',
    'libghttp-1.0.9.tgz'
  ]
},
{
  InstalledVersionQuery => '<%Prefix%>/bin/xml2-config --version',
  Requires => [],
  DistBaseName => 'libxml2-2.4.13',
  DetectedRequires => [],
  Name => 'libxml2',
  Style => 'GNOME',
  URIs => '<%GNOME_URIs%>/libxml/<%DistFileNames%>',
  Version => '2.4.13',
  DistFileNames => [
    'libxml2-2.4.13.tar.gz',
    'libxml2-2.4.13.tgz'
  ]
},
{
  InstalledVersionQuery => '<%Prefix%>/bin/xslt-config --version',
  Actions => [
    {
      Comment => 'Skip known test failures so FreeBSD\'s make will not die',
      Phase => 'unpack',
      Action => 'DeleteFile',
      Path => 'tests/exslt/sets/has-same-node.1.xsl'
    },
    {
      Comment => 'Skip known test failures so FreeBSD\'s make will not die',
      Phase => 'unpack',
      Action => 'DeleteFile',
      Path => 'tests/exslt/sets/has-same-node.1.xml'
    }
  ],
  Requires => 'libxml2',
  Version => '1.0.10',
  DistBaseName => 'libxslt-1.0.10',
  DetectedRequires => [],
  Name => 'libxslt',
  DistFileNames => [
    'libxslt-1.0.10.tar.gz',
    'libxslt-1.0.10.tgz'
  ],
  Style => 'GNOME'
},
{
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    },
    {
      Comment => 'Forcing load of URI::URL now that LWP::UserAgent doesn\'t',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/require URI::URL;\\n/m\' lib/LWP/UserAgent.pm'
    },
    {
      Comment => 'Skip live tests in case of poor connectivity',
      IfExists => 1,
      Phase => 'configure',
      Action => 'DeleteFile',
      Path => 't/live/ENABLED'
    },
    {
      Comment => 'Skip tests that fail on 5.00503',
      Phase => 'unpack',
      Action => 'DeleteFile',
      Path => 't/base/date.t'
    },
    {
      Comment => 'Skip tests that fail on 5.00503',
      Phase => 'unpack',
      Action => 'DeleteFile',
      Path => 't/base/listing.t'
    },
    {
      Comment => 'Skip tests that fail',
      Phase => 'unpack',
      Action => 'DeleteFile',
      Path => 't/html/form.t'
    }
  ],
  Requires => 'MIME::Base64',
  DistBaseName => 'libwww-perl-5.64',
  Name => 'libwww-perl',
  DetectedRequires => [
    'URI',
    'Net::FTP',
    'HTML::HeadParser',
    'Digest::MD5',
    'MIME::Base64'
  ],
  Style => 'PerlModule',
  VersionFrom => 'LWP',
  URIs => '<%CPAN_URIs%>/G/GA/GAAS/<%DistFileNames%>',
  Version => '5.64',
  DistFileNames => 'libwww-perl-5.64.tar.gz'
},
{
  ExternalComponents => 'apache',
  InstalledVersionQuery => '<%Prefix%>/bin/httpd -l | perl -lne \'print "<%Version%>" if /mod_perl/\'',
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    },
    {
      Comment => 'Force Makefile.PL to see our libs',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s{^(\\$ENV\\{PERL5LIB\\}\\s*=\\s*(["\\x27]).*)\\2}{$1:<%Prefix%>/lib$2}m\' Makefile.PL '
    },
    {
      Comment => 'Force make test to obey PERL5LIB',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s{^(\\t\\s*)(.*\\(MP_TEST_SCRIPT\\))}{$1\\$(PERL5LIB) $2}m\' Makefile.PL '
    }
  ],
  Requires => [
    'libwww-perl',
    'URI',
    'HTML::HeadParser'
  ],
  DistBaseName => 'mod_perl-1.26',
  DetectedRequires => [],
  Name => 'mod_perl',
  Style => 'PerlModule',
  ConfigParms => {
    PREFIX => '<%Prefix%>',
    INSTALLSITEARCH => '<%Prefix%>/lib',
    EVERYTHING => 1,
    INSTALLSITELIB => '<%Prefix%>/lib',
    DO_HTTPD => 1,
    USE_APACI => 1,
    APACHE_PREFIX => '<%Prefix%>'
  },
  MinVersion => '1.24_01',
  URIs => 'http://perl.apache.org/dist/<%DistFileNames%>',
  Version => '1.26',
  DistFileNames => 'mod_perl-1.26.tar.gz'
},
{
  MinVersion => '1.10',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    },
    {
      Phase => 'unpack',
      Action => 'DeleteFile',
      Path => 't/heuristic.t'
    }
  ],
  Version => '1.18',
  DistBaseName => 'URI-1.18',
  Name => 'URI',
  DetectedRequires => 'MIME::Base64',
  DistFileNames => 'URI-1.18.tar.gz',
  Style => 'PerlModule'
},
{
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    },
    {
      Comment => 'Preventing XML::LibXML from trying to update ParserDetails.ini',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -i.bak -ne \'$i += /^sub MY::install/; print unless $i == 1; $i += /^}/ if $i; \' Makefile.PL'
    }
  ],
  Requires => 'libxml2',
  DistBaseName => 'XML-LibXML-1.31',
  Name => 'XML::LibXML',
  DetectedRequires => 'XML::SAX',
  Style => 'PerlModule',
  DistName => 'XML-LibXML',
  MinVersion => '1.31',
  Version => '1.31',
  DistFileNames => 'XML-LibXML-1.31.tar.gz'
},
{
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    },
    {
      Comment => 'Keeping XML::LibXSLT from blowing up',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -i.bak -ne \'$i += /^require XML::LibXML/; print unless $i == 1; $i =0 if $i && /^}/; \' Makefile.PL'
    }
  ],
  Requires => 'libxslt',
  DistBaseName => 'XML-LibXSLT-1.31',
  Name => 'XML::LibXSLT',
  DetectedRequires => 'XML::LibXML',
  Style => 'PerlModule',
  DistName => 'XML-LibXSLT',
  MinVersion => '1.31',
  Version => '1.31',
  DistFileNames => 'XML-LibXSLT-1.31.tar.gz'
},
{
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    },
    {
      Comment => 'Skip tests that prompt user',
      Phase => 'unpack',
      Action => 'DeleteFile',
      Path => 'test.pl'
    }
  ],
  Version => '0.09',
  DistBaseName => 'Geo-Weather-0.09',
  DetectedRequires => [],
  Name => 'Geo::Weather',
  DistFileNames => 'Geo-Weather-0.09.tar.gz',
  Style => 'PerlModule'
},
{
  Actions => [
    {
      Paths => '<%DistDir%>',
      Dest => '<%Prefix%>',
      Phase => 'install',
      Action => 'CopyFiles'
    },
    {
      Body => 'use lib qw( <%Prefix%>/lib );

1;
',
      Phase => 'install',
      Action => 'WriteFile',
      Path => '<%Prefix%>/startup.pl'
    },
    {
      Comment => 'Run only 1 server process',
      Phase => 'install',
      Action => 'RunCmd',
      CmdLine => 'perl -i.bak -pe \'s/\\d+/1/ if /^(M..Spare|Start)Servers/\' <%Prefix%>/conf/httpd.conf '
    },
    {
      IfNotAlreadyInFile => 1,
      Body => '##
## Init the httpd to use our "private install" libraries
##
PerlRequire startup.pl

##
## AxKit Configuration
##
PerlModule AxKit

<Directory "<%Prefix%>/htdocs">
    Options -All +Indexes +FollowSymLinks

    # Tell mod_dir to translate / to /index.xml or /index.xsp
    DirectoryIndex index.xml index.xsp
    AddHandler axkit .xml .xsp

    AxDebugLevel 10

    AxGzipOutput Off

    AxAddXSPTaglib AxKit::XSP::Util
    AxAddXSPTaglib AxKit::XSP::Param
    AxAddXSPTaglib My::WeatherTaglib

    AxAddStyleMap application/x-xsp \\
                  Apache::AxKit::Language::XSP

    AxAddStyleMap text/xsl \\
                  Apache::AxKit::Language::LibXSLT
</Directory>
',
      Phase => 'install',
      Action => 'AppendToFile',
      Path => '<%Prefix%>/conf/httpd.conf'
    },
    {
      Body => '<?xml-stylesheet href="NULL" type="application/x-xsp"?>
<xsp:page
    xmlns:xsp="http://www.apache.org/1999/XSP/Core"
    xmlns:util="http://apache.org/xsp/util/v1"
>
  <html>
    <body>
      <p>Hi! It\'s <util:time format="%H:%M:%S"/>.</p>
    </body>
  </html>
</xsp:page>
',
      Phase => 'install',
      Action => 'WriteFile',
      Path => '<%Prefix%>/htdocs/index.xsp'
    }
  ],
  Requires => [
    'XML::LibXSLT',
    'XML::LibXML',
    'AxKit',
    'Geo::Weather',
    'AxKit::XSP::Param'
  ],
  Version => 1,
  IsVirtual => 1,
  DistBaseName => 'AxKitDemo-1',
  DetectedRequires => [],
  Name => 'AxKitDemo',
  DistFileNames => [
    'AxKitDemo-1.tar.gz',
    'AxKitDemo-1.tgz'
  ],
  Style => 'RawDirectory'
},
{
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => '1',
  Version => '1.019',
  DistBaseName => 'Apache-Filter-1.019',
  Name => 'Apache::Filter',
  DetectedRequires => 'mod_perl',
  DistFileNames => 'Apache-Filter-1.019.tar.gz',
  Style => 'PerlModule'
},
{
  MinVersion => '2.1',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => '1',
  Version => '2.12',
  DistBaseName => 'MIME-Base64-2.12',
  DetectedRequires => [],
  Name => 'MIME::Base64',
  DistFileNames => 'MIME-Base64-2.12.tar.gz',
  Style => 'PerlModule'
},
{
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => '1',
  DistBaseName => 'HTML-Parser-3.26',
  Name => 'HTML::HeadParser',
  DetectedRequires => 'HTML::Tagset',
  Style => 'PerlModule',
  MinVersion => '0',
  Version => '2.17',
  DistFileNames => 'HTML-Parser-3.26.tar.gz'
},
{
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  Version => '1.4',
  DistBaseName => 'AxKit-XSP-Param-1.4',
  Name => 'AxKit::XSP::Param',
  DetectedRequires => 'AxKit',
  DistFileNames => 'AxKit-XSP-Param-1.4.tar.gz',
  Style => 'PerlModule'
},
{
  MinVersion => '2.09',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  Version => '2.16',
  DistBaseName => 'Digest-MD5-2.16',
  DetectedRequires => [],
  Name => 'Digest::MD5',
  DistFileNames => 'Digest-MD5-2.16.tar.gz',
  Style => 'PerlModule'
},
{
  MinVersion => '0.13',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  Version => '0.15',
  DistBaseName => 'Error-0.15',
  DetectedRequires => [],
  Name => 'Error',
  DistFileNames => 'Error-0.15.tar.gz',
  Style => 'PerlModule'
},
{
  MinVersion => '0',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  Version => '1.16',
  DistBaseName => 'Compress-Zlib-1.16',
  DetectedRequires => [],
  Name => 'Compress::Zlib',
  DistFileNames => 'Compress-Zlib-1.16.tar.gz',
  Style => 'PerlModule'
},
{
  MinVersion => '2.58',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  Version => '2.64',
  DistBaseName => 'libnet-1.11',
  DetectedRequires => [],
  Name => 'Net::FTP',
  DistFileNames => 'libnet-1.11.tar.gz',
  Style => 'PerlModule'
},
{
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  DistBaseName => 'XML-SAX-0.10',
  Name => 'XML::SAX',
  DetectedRequires => [
    'File::Temp',
    'XML::NamespaceSupport'
  ],
  Style => 'PerlModule',
  MinVersion => '0',
  Version => '0.10',
  DistFileNames => 'XML-SAX-0.10.tar.gz'
},
{
  MinVersion => '3',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  Version => '3.03',
  DistBaseName => 'HTML-Tagset-3.03',
  DetectedRequires => [],
  Name => 'HTML::Tagset',
  DistFileNames => 'HTML-Tagset-3.03.tar.gz',
  Style => 'PerlModule'
},
{
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  DistBaseName => 'File-Temp-0.12',
  Name => 'File::Temp',
  DetectedRequires => 'File::Spec',
  Style => 'PerlModule',
  MinVersion => '0',
  Version => '0.12',
  DistFileNames => 'File-Temp-0.12.tar.gz'
},
{
  MinVersion => '0.03',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  Version => '1.04',
  DistBaseName => 'XML-NamespaceSupport-1.04',
  DetectedRequires => [],
  Name => 'XML::NamespaceSupport',
  DistFileNames => 'XML-NamespaceSupport-1.04.tar.gz',
  Style => 'PerlModule'
},
{
  MinVersion => '0.8',
  Requires => [],
  Actions => [
    {
      Comment => 'Preventing perl Makefile.PL from searching in site_perl or site/lib',
      Phase => 'unpack',
      Action => 'RunCmd',
      CmdLine => 'perl -0777 -i.bak -pe \'s/^(?!#)/BEGIN { use Config; \\@INC = grep index( \\$_, defined \\$Config{sitelib_stem}? \\$Config{sitelib_stem} : \\$Config{sitelibexp}  ), \\@INC }\\n/m\' Makefile.PL'
    }
  ],
  AutoGenerated => 1,
  Version => '0.82',
  DistBaseName => 'File-Spec-0.82',
  DetectedRequires => [],
  Name => 'File::Spec',
  DistFileNames => 'File-Spec-0.82.tar.gz',
  Style => 'PerlModule'
}] # End Packages
,
} );
#};
$m->get_package( "AxKitDemo" )->install();


=for nothing
Just in case the POD isn't closed off...

=cut

$INC{""} = "inlined" if length "";
chdir $original_cwd or die $!;
}

}
