diff options
author | Jos I. Boumans <kane@dwim.org> | 2007-05-04 16:41:39 +0200 |
---|---|---|
committer | Rafael Garcia-Suarez <rgarciasuarez@gmail.com> | 2007-05-04 14:33:07 +0000 |
commit | 9b4bd8544a9d420b5b0396e6e2c5381e0f8a6443 (patch) | |
tree | 7d13f7b82dc6d0d8d745616b240c54056fec7737 /lib/CPANPLUS | |
parent | 33d1b1220d358b25bf635f693d096ab32d25860f (diff) | |
download | perl-9b4bd8544a9d420b5b0396e6e2c5381e0f8a6443.tar.gz |
Add CPANPLUS::Dist::Build to the core
From: "Jos I. Boumans" <kane@dwim.org>
Message-Id: <58AAEC18-D5B6-4840-9FA5-B121D95446A3@dwim.org>
p4raw-id: //depot/perl@31140
Diffstat (limited to 'lib/CPANPLUS')
-rw-r--r-- | lib/CPANPLUS/Dist/Build.pm | 791 | ||||
-rw-r--r-- | lib/CPANPLUS/Dist/Build/Constants.pm | 34 | ||||
-rw-r--r-- | lib/CPANPLUS/Dist/Build/t/01_CPANPLUS-Dist-Build-Constants.t | 25 | ||||
-rw-r--r-- | lib/CPANPLUS/Dist/Build/t/02_CPANPLUS-Dist-Build.t | 235 | ||||
-rw-r--r-- | lib/CPANPLUS/Dist/Build/t/inc/conf.pl | 201 | ||||
-rw-r--r-- | lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed | 35 | ||||
-rw-r--r-- | lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed | 50 |
7 files changed, 1371 insertions, 0 deletions
diff --git a/lib/CPANPLUS/Dist/Build.pm b/lib/CPANPLUS/Dist/Build.pm new file mode 100644 index 0000000000..4d4aec03a1 --- /dev/null +++ b/lib/CPANPLUS/Dist/Build.pm @@ -0,0 +1,791 @@ +package CPANPLUS::Dist::Build; + +use strict; +use vars qw[@ISA $STATUS $VERSION]; +@ISA = qw[CPANPLUS::Dist]; + +use CPANPLUS::inc; +use CPANPLUS::Internals::Constants; + +### these constants were exported by CPANPLUS::Internals::Constants +### in previous versions.. they do the same though. If we want to have +### a normal 'use' here, up the dependency to CPANPLUS 0.056 or higher +BEGIN { + require CPANPLUS::Dist::Build::Constants; + CPANPLUS::Dist::Build::Constants->import() + if not __PACKAGE__->can('BUILD') && __PACKAGE__->can('BUILD_DIR'); +} + +use CPANPLUS::Error; + +use Config; +use FileHandle; +use Cwd; + +use IPC::Cmd qw[run]; +use Params::Check qw[check]; +use Module::Load::Conditional qw[can_load check_install]; +use Locale::Maketext::Simple Class => 'CPANPLUS', Style => 'gettext'; + +local $Params::Check::VERBOSE = 1; + +$VERSION = '0.06'; + +=pod + +=head1 NAME + +CPANPLUS::Dist::Build + +=head1 SYNOPSIS + + my $build = CPANPLUS::Dist->new( + format => 'CPANPLUS::Dist::Build', + module => $modobj, + ); + + $build->prepare; # runs Module::Build->new_from_context; + $build->create; # runs build && build test + $build->install; # runs build install + + +=head1 DESCRIPTION + +C<CPANPLUS::Dist::Build> is a distribution class for C<Module::Build> +related modules. +Using this package, you can create, install and uninstall perl +modules. It inherits from C<CPANPLUS::Dist>. + +Normal users won't have to worry about the interface to this module, +as it functions transparently as a plug-in to C<CPANPLUS> and will +just C<Do The Right Thing> when it's loaded. + +=head1 ACCESSORS + +=over 4 + +=item parent() + +Returns the C<CPANPLUS::Module> object that parented this object. + +=item status() + +Returns the C<Object::Accessor> object that keeps the status for +this module. + +=back + +=head1 STATUS ACCESSORS + +All accessors can be accessed as follows: + $build->status->ACCESSOR + +=over 4 + +=item build_pl () + +Location of the Build file. +Set to 0 explicitly if something went wrong. + +=item build () + +BOOL indicating if the C<Build> command was successful. + +=item test () + +BOOL indicating if the C<Build test> command was successful. + +=item prepared () + +BOOL indicating if the C<prepare> call exited succesfully +This gets set after C<perl Build.PL> + +=item distdir () + +Full path to the directory in which the C<prepare> call took place, +set after a call to C<prepare>. + +=item created () + +BOOL indicating if the C<create> call exited succesfully. This gets +set after C<Build> and C<Build test>. + +=item installed () + +BOOL indicating if the module was installed. This gets set after +C<Build install> exits successfully. + +=item uninstalled () + +BOOL indicating if the module was uninstalled properly. + +=item _create_args () + +Storage of the arguments passed to C<create> for this object. Used +for recursive calls when satisfying prerequisites. + +=item _install_args () + +Storage of the arguments passed to C<install> for this object. Used +for recursive calls when satisfying prerequisites. + +=item _mb_object () + +Storage of the C<Module::Build> object we used for this installation. + +=back + +=cut + + +=head1 METHODS + +=head2 $bool = CPANPLUS::Dist::Build->format_available(); + +Returns a boolean indicating whether or not you can use this package +to create and install modules in your environment. + +=cut + +### check if the format is available ### +sub format_available { + my $mod = "Module::Build"; + unless( can_load( modules => { $mod => '0.2611' } ) ) { + error( loc( "You do not have '%1' -- '%2' not available", + $mod, __PACKAGE__ ) ); + return; + } + + return 1; +} + + +=head2 $bool = $dist->init(); + +Sets up the C<CPANPLUS::Dist::Build> object for use. +Effectively creates all the needed status accessors. + +Called automatically whenever you create a new C<CPANPLUS::Dist> object. + +=cut + +sub init { + my $dist = shift; + my $status = $dist->status; + + $status->mk_accessors(qw[build_pl build test created installed uninstalled + _create_args _install_args _prepare_args + _mb_object _buildflags + ]); + + ### just in case 'format_available' didn't get called + require Module::Build; + + return 1; +} + +=pod + +=head2 $bool = $dist->prepare([perl => '/path/to/perl', buildflags => 'EXTRA=FLAGS', force => BOOL, verbose => BOOL]) + +C<prepare> prepares a distribution, running C<Module::Build>'s +C<new_from_context> method, and establishing any prerequisites this +distribution has. + +When running C<< Module::Build->new_from_context >>, the environment +variable C<PERL5_CPANPLUS_IS_EXECUTING> will be set to the full path +of the C<Build.PL> that is being executed. This enables any code inside +the C<Build.PL> to know that it is being installed via CPANPLUS. + +After a succcesfull C<prepare> you may call C<create> to create the +distribution, followed by C<install> to actually install it. + +Returns true on success and false on failure. + +=cut + +sub prepare { + ### just in case you already did a create call for this module object + ### just via a different dist object + my $dist = shift; + my $self = $dist->parent; + + ### we're also the cpan_dist, since we don't need to have anything + ### prepared from another installer + $dist = $self->status->dist_cpan if $self->status->dist_cpan; + $self->status->dist_cpan( $dist ) unless $self->status->dist_cpan; + + my $cb = $self->parent; + my $conf = $cb->configure_object; + my %hash = @_; + + my $dir; + unless( $dir = $self->status->extract ) { + error( loc( "No dir found to operate on!" ) ); + return; + } + + my $args; + my( $force, $verbose, $buildflags, $perl); + { local $Params::Check::ALLOW_UNKNOWN = 1; + my $tmpl = { + force => { default => $conf->get_conf('force'), + store => \$force }, + verbose => { default => $conf->get_conf('verbose'), + store => \$verbose }, + perl => { default => $^X, store => \$perl }, + buildflags => { default => $conf->get_conf('buildflags'), + store => \$buildflags }, + }; + + $args = check( $tmpl, \%hash ) or return; + } + + return 1 if $dist->status->prepared && !$force; + + $dist->status->_prepare_args( $args ); + + ### chdir to work directory ### + my $orig = cwd(); + unless( $cb->_chdir( dir => $dir ) ) { + error( loc( "Could not chdir to build directory '%1'", $dir ) ); + return; + } + + ### by now we've loaded module::build, and we're using the API, so + ### it's safe to remove CPANPLUS::inc from our inc path, especially + ### because it can trip up tests run under taint (just like EU::MM). + ### turn off our PERL5OPT so no modules from CPANPLUS::inc get + ### included in make test -- it should build without. + ### also, modules that run in taint mode break if we leave + ### our code ref in perl5opt + ### XXX we've removed the ENV settings from cp::inc, so only need + ### to reset the @INC + #local $ENV{PERL5OPT} = CPANPLUS::inc->original_perl5opt; + #local $ENV{PERL5LIB} = CPANPLUS::inc->original_perl5lib; + local @INC = CPANPLUS::inc->original_inc; + + ### this will generate warnings under anything lower than M::B 0.2606 + my %buildflags = $dist->_buildflags_as_hash( $buildflags ); + $dist->status->_buildflags( $buildflags ); + + my $fail; + RUN: { + # Wrap the exception that may be thrown here (should likely be + # done at a much higher level). + my $mb = eval { + my $env = 'ENV_CPANPLUS_IS_EXECUTING'; + local $ENV{$env} = BUILD_PL->( $dir ); + Module::Build->new_from_context( %buildflags ) + }; + if( !$mb or $@ ) { + error(loc("Could not create Module::Build object: %1","$@")); + $fail++; last RUN; + } + + $dist->status->_mb_object( $mb ); + + $self->status->prereqs( $dist->_find_prereqs( verbose => $verbose ) ); + + } + + ### send out test report? ### + if( $fail and $conf->get_conf('cpantest') ) { + $cb->_send_report( + module => $self, + failed => $fail, + buffer => CPANPLUS::Error->stack_as_string, + verbose => $verbose, + force => $force, + ) or error(loc("Failed to send test report for '%1'", + $self->module ) ); + } + + unless( $cb->_chdir( dir => $orig ) ) { + error( loc( "Could not chdir back to start dir '%1'", $orig ) ); + } + + ### save where we wrote this stuff -- same as extract dir in normal + ### installer circumstances + $dist->status->distdir( $self->status->extract ); + + return $dist->status->prepared( $fail ? 0 : 1 ); +} + +sub _find_prereqs { + my $dist = shift; + my $mb = $dist->status->_mb_object; + my $self = $dist->parent; + my $cb = $self->parent; + + my $prereqs = {}; + foreach my $type ('requires', 'build_requires') { + my $p = $mb->$type() || {}; + $prereqs->{$_} = $p->{$_} foreach keys %$p; + } + + ### allows for a user defined callback to filter the prerequisite + ### list as they see fit, to remove (or add) any prereqs they see + ### fit. The default installed callback will return the hashref in + ### an unmodified form + ### this callback got added after cpanplus 0.0562, so use a 'can' + ### to find out if it's supported. For older versions, we'll just + ### return the hashref as is ourselves. + my $href = $cb->_callbacks->can('filter_prereqs') + ? $cb->_callbacks->filter_prereqs->( $cb, $prereqs ) + : $prereqs; + + $self->status->prereqs( $href ); + + ### make sure it's not the same ref + return { %$href }; +} + +sub prereq_satisfied { + # Return true if this prereq is satisfied. Return false if it's + # not. Also issue an error if the latest CPAN version doesn't + # satisfy it. + + my ($dist, %args) = @_; + my $mb = $dist->status->_mb_object; + my $cb = $dist->parent->parent; + my $mod = $args{modobj}->module; + + my $status = $mb->check_installed_status($mod, $args{version}); + return 1 if $status->{ok}; + + # Check the latest version from the CPAN index + { + no strict 'refs'; + local ${$mod . '::VERSION'} = $args{modobj}->version; + $status = $mb->check_installed_status($mod, $args{version}); + } + unless( $status->{ok} ) { + error(loc("This distribution depends on $mod, but the latest version of $mod on CPAN ". + "doesn't satisfy the specific version dependency ($args{version}). ". + "Please try to resolve this dependency manually.")); + } + + return 0; +} + +=pod + +=head2 $dist->create([perl => '/path/to/perl', buildflags => 'EXTRA=FLAGS', prereq_target => TARGET, force => BOOL, verbose => BOOL, skiptest => BOOL]) + +C<create> preps a distribution for installation. This means it will +run C<Build> and C<Build test>, via the C<Module::Build> API. +This will also satisfy any prerequisites the module may have. + +If you set C<skiptest> to true, it will skip the C<Build test> stage. +If you set C<force> to true, it will go over all the stages of the +C<Build> process again, ignoring any previously cached results. It +will also ignore a bad return value from C<Build test> and still allow +the operation to return true. + +Returns true on success and false on failure. + +You may then call C<< $dist->install >> on the object to actually +install it. + +=cut + +sub create { + ### just in case you already did a create call for this module object + ### just via a different dist object + my $dist = shift; + my $self = $dist->parent; + + ### we're also the cpan_dist, since we don't need to have anything + ### prepared from another installer + $dist = $self->status->dist_cpan if $self->status->dist_cpan; + $self->status->dist_cpan( $dist ) unless $self->status->dist_cpan; + + my $cb = $self->parent; + my $conf = $cb->configure_object; + my $mb = $dist->status->_mb_object; + my %hash = @_; + + my $dir; + unless( $dir = $self->status->extract ) { + error( loc( "No dir found to operate on!" ) ); + return; + } + + my $args; + my( $force, $verbose, $buildflags, $skiptest, $prereq_target, + $perl, $prereq_format, $prereq_build); + { local $Params::Check::ALLOW_UNKNOWN = 1; + my $tmpl = { + force => { default => $conf->get_conf('force'), + store => \$force }, + verbose => { default => $conf->get_conf('verbose'), + store => \$verbose }, + perl => { default => $^X, store => \$perl }, + buildflags => { default => $conf->get_conf('buildflags'), + store => \$buildflags }, + skiptest => { default => $conf->get_conf('skiptest'), + store => \$skiptest }, + prereq_target => { default => '', store => \$prereq_target }, + ### don't set the default format to 'build' -- that is wrong! + prereq_format => { #default => $self->status->installer_type, + default => '', + store => \$prereq_format }, + prereq_build => { default => 0, store => \$prereq_build }, + }; + + $args = check( $tmpl, \%hash ) or return; + } + + return 1 if $dist->status->created && !$force; + + $dist->status->_create_args( $args ); + + ### is this dist prepared? + unless( $dist->status->prepared ) { + error( loc( "You have not successfully prepared a '%2' distribution ". + "yet -- cannot create yet", __PACKAGE__ ) ); + return; + } + + ### chdir to work directory ### + my $orig = cwd(); + unless( $cb->_chdir( dir => $dir ) ) { + error( loc( "Could not chdir to build directory '%1'", $dir ) ); + return; + } + + ### by now we've loaded module::build, and we're using the API, so + ### it's safe to remove CPANPLUS::inc from our inc path, especially + ### because it can trip up tests run under taint (just like EU::MM). + ### turn off our PERL5OPT so no modules from CPANPLUS::inc get + ### included in make test -- it should build without. + ### also, modules that run in taint mode break if we leave + ### our code ref in perl5opt + ### XXX we've removed the ENV settings from cp::inc, so only need + ### to reset the @INC + #local $ENV{PERL5OPT} = CPANPLUS::inc->original_perl5opt; + #local $ENV{PERL5LIB} = CPANPLUS::inc->original_perl5lib; + local @INC = CPANPLUS::inc->original_inc; + + ### but do it *before* the new_from_context, as M::B seems + ### to be actually running the file... + ### an unshift in the block seems to be ignored.. somehow... + #{ my $lib = $self->best_path_to_module_build; + # unshift @INC, $lib if $lib; + #} + unshift @INC, $self->best_path_to_module_build + if $self->best_path_to_module_build; + + ### this will generate warnings under anything lower than M::B 0.2606 + my %buildflags = $dist->_buildflags_as_hash( $buildflags ); + $dist->status->_buildflags( $buildflags ); + + my $fail; my $prereq_fail; my $test_fail; + RUN: { + + ### this will set the directory back to the start + ### dir, so we must chdir /again/ + my $ok = $dist->_resolve_prereqs( + force => $force, + format => $prereq_format, + verbose => $verbose, + prereqs => $self->status->prereqs, + target => $prereq_target, + prereq_build => $prereq_build, + ); + + unless( $cb->_chdir( dir => $dir ) ) { + error( loc( "Could not chdir to build directory '%1'", $dir ) ); + return; + } + + unless( $ok ) { + #### use $dist->flush to reset the cache ### + error( loc( "Unable to satisfy prerequisites for '%1' " . + "-- aborting install", $self->module ) ); + $dist->status->build(0); + $fail++; $prereq_fail++; + last RUN; + } + + eval { $mb->dispatch('build', %buildflags) }; + if( $@ ) { + error(loc("Could not run '%1': %2", 'Build', "$@")); + $dist->status->build(0); + $fail++; last RUN; + } + + $dist->status->build(1); + + ### add this directory to your lib ### + $cb->_add_to_includepath( + directories => [ BLIB_LIBDIR->( $self->status->extract ) ] + ); + + ### this buffer will not include what tests failed due to a + ### M::B/Test::Harness bug. Reported as #9793 with patch + ### against 0.2607 on 26/1/2005 + unless( $skiptest ) { + eval { $mb->dispatch('test', %buildflags) }; + if( $@ ) { + error(loc("Could not run '%1': %2", 'Build test', "$@")); + + ### mark specifically *test* failure.. so we dont + ### send success on force... + $test_fail++; + + unless($force) { + $dist->status->test(0); + $fail++; last RUN; + } + } else { + $dist->status->test(1); + } + } else { + msg(loc("Tests skipped"), $verbose); + } + } + + unless( $cb->_chdir( dir => $orig ) ) { + error( loc( "Could not chdir back to start dir '%1'", $orig ) ); + } + + ### send out test report? ### + if( $conf->get_conf('cpantest') and not $prereq_fail ) { + $cb->_send_report( + module => $self, + failed => $test_fail || $fail, + buffer => CPANPLUS::Error->stack_as_string, + verbose => $verbose, + force => $force, + tests_skipped => $skiptest, + ) or error(loc("Failed to send test report for '%1'", + $self->module ) ); + } + + return $dist->status->created( $fail ? 0 : 1 ); +} + +=head2 $dist->install([verbose => BOOL, perl => /path/to/perl]) + +Actually installs the created dist. + +Returns true on success and false on failure. + +=cut + +sub install { + ### just in case you already did a create call for this module object + ### just via a different dist object + my $dist = shift; + my $self = $dist->parent; + + ### we're also the cpan_dist, since we don't need to have anything + ### prepared from another installer + $dist = $self->status->dist_cpan if $self->status->dist_cpan; + my $mb = $dist->status->_mb_object; + + my $cb = $self->parent; + my $conf = $cb->configure_object; + my %hash = @_; + + + my $verbose; my $perl; my $force; + { local $Params::Check::ALLOW_UNKNOWN = 1; + my $tmpl = { + verbose => { default => $conf->get_conf('verbose'), + store => \$verbose }, + force => { default => $conf->get_conf('force'), + store => \$force }, + perl => { default => $^X, store => \$perl }, + }; + + my $args = check( $tmpl, \%hash ) or return; + $dist->status->_install_args( $args ); + } + + my $dir; + unless( $dir = $self->status->extract ) { + error( loc( "No dir found to operate on!" ) ); + return; + } + + my $orig = cwd(); + + unless( $cb->_chdir( dir => $dir ) ) { + error( loc( "Could not chdir to build directory '%1'", $dir ) ); + return; + } + + ### value set and false -- means failure ### + if( defined $self->status->installed && + !$self->status->installed && !$force + ) { + error( loc( "Module '%1' has failed to install before this session " . + "-- aborting install", $self->module ) ); + return; + } + + my $fail; + my $buildflags = $dist->status->_buildflags; + ### hmm, how is this going to deal with sudo? + ### for now, check effective uid, if it's not root, + ### shell out, otherwise use the method + if( $> ) { + + ### don't worry about loading the right version of M::B anymore + ### the 'new_from_context' already added the 'right' path to + ### M::B at the top of the build.pl + my $cmd = [$perl, BUILD->($dir), 'install', $buildflags]; + my $sudo = $conf->get_program('sudo'); + unshift @$cmd, $sudo if $sudo; + + + my $buffer; + unless( scalar run( command => $cmd, + buffer => \$buffer, + verbose => $verbose ) + ) { + error(loc("Could not run '%1': %2", 'Build install', $buffer)); + $fail++; + } + } else { + my %buildflags = $dist->_buildflags_as_hash($buildflags); + + eval { $mb->dispatch('install', %buildflags) }; + if( $@ ) { + error(loc("Could not run '%1': %2", 'Build install', "$@")); + $fail++; + } + } + + + unless( $cb->_chdir( dir => $orig ) ) { + error( loc( "Could not chdir back to start dir '%1'", $orig ) ); + } + + return $dist->status->installed( $fail ? 0 : 1 ); +} + +### returns the string 'foo=bar zot=quux' as (foo => bar, zot => quux) +sub _buildflags_as_hash { + my $self = shift; + my $flags = shift or return; + + my @argv = Module::Build->split_like_shell($flags); + my ($argv) = Module::Build->read_args(@argv); + + return %$argv; +} + + +sub dist_dir { + ### just in case you already did a create call for this module object + ### just via a different dist object + my $dist = shift; + my $self = $dist->parent; + + ### we're also the cpan_dist, since we don't need to have anything + ### prepared from another installer + $dist = $self->status->dist_cpan if $self->status->dist_cpan; + my $mb = $dist->status->_mb_object; + + my $cb = $self->parent; + my $conf = $cb->configure_object; + my %hash = @_; + + + my $dir; + unless( $dir = $self->status->extract ) { + error( loc( "No dir found to operate on!" ) ); + return; + } + + ### chdir to work directory ### + my $orig = cwd(); + unless( $cb->_chdir( dir => $dir ) ) { + error( loc( "Could not chdir to build directory '%1'", $dir ) ); + return; + } + + my $fail; my $distdir; + TRY: { + $dist->prepare( @_ ) or (++$fail, last TRY); + + + eval { $mb->dispatch('distdir') }; + if( $@ ) { + error(loc("Could not run '%1': %2", 'Build distdir', "$@")); + ++$fail, last TRY; + } + + ### /path/to/Foo-Bar-1.2/Foo-Bar-1.2 + $distdir = File::Spec->catdir( $dir, $self->package_name . '-' . + $self->package_version ); + + unless( -d $distdir ) { + error(loc("Do not know where '%1' got created", 'distdir')); + ++$fail, last TRY; + } + } + + unless( $cb->_chdir( dir => $orig ) ) { + error( loc( "Could not chdir to start directory '%1'", $orig ) ); + return; + } + + return if $fail; + return $distdir; +} + +=head1 KNOWN ISSUES + +Below are some of the known issues with Module::Build, that we hope +the authors will resolve at some point, so we can make full use of +Module::Build's power. +The number listed is the bug number on C<rt.cpan.org>. + +=over 4 + +=item * Module::Build can not be upgraded using its own API (#13169) + +This is due to the fact that the Build file insists on adding a path +to C<@INC> which force the loading of the C<not yet installed> +Module::Build when it shells out to run it's own build procedure: + +=item * Module::Build does not provide access to install history (#9793) + +C<Module::Build> runs the create, test and install procedures in it's +own processes, but does not provide access to any diagnostic messages of +those processes. As an end result, we can not offer these diagnostic +messages when, for example, reporting automated build failures to sites +like C<testers.cpan.org>. + +=back + +=head1 AUTHOR + +Originally by Jos Boumans E<lt>kane@cpan.orgE<gt>. Brought to working +condition and currently maintained by Ken Williams E<lt>kwilliams@cpan.orgE<gt>. + +=head1 COPYRIGHT + +The CPAN++ interface (of which this module is a part of) is +copyright (c) 2001, 2002, 2003, 2004, 2005 Jos Boumans E<lt>kane@cpan.orgE<gt>. +All rights reserved. + +This library is free software; +you may redistribute and/or modify it under the same +terms as Perl itself. + +=cut + +1; + +# Local variables: +# c-indentation-style: bsd +# c-basic-offset: 4 +# indent-tabs-mode: nil +# End: +# vim: expandtab shiftwidth=4: diff --git a/lib/CPANPLUS/Dist/Build/Constants.pm b/lib/CPANPLUS/Dist/Build/Constants.pm new file mode 100644 index 0000000000..1a089ff579 --- /dev/null +++ b/lib/CPANPLUS/Dist/Build/Constants.pm @@ -0,0 +1,34 @@ +package CPANPLUS::Dist::Build::Constants; + +use strict; +use File::Spec; + +BEGIN { + + require Exporter; + use vars qw[$VERSION @ISA @EXPORT]; + + $VERSION = 0.01; + @ISA = qw[Exporter]; + @EXPORT = qw[ BUILD_DIR BUILD ]; +} + + +use constant BUILD_DIR => sub { return @_ + ? File::Spec->catdir($_[0], '_build') + : '_build'; + }; +use constant BUILD => sub { return @_ + ? File::Spec->catfile($_[0], 'Build') + : 'Build'; + }; + +1; + + +# Local variables: +# c-indentation-style: bsd +# c-basic-offset: 4 +# indent-tabs-mode: nil +# End: +# vim: expandtab shiftwidth=4: diff --git a/lib/CPANPLUS/Dist/Build/t/01_CPANPLUS-Dist-Build-Constants.t b/lib/CPANPLUS/Dist/Build/t/01_CPANPLUS-Dist-Build-Constants.t new file mode 100644 index 0000000000..c7cb348a3e --- /dev/null +++ b/lib/CPANPLUS/Dist/Build/t/01_CPANPLUS-Dist-Build-Constants.t @@ -0,0 +1,25 @@ +BEGIN { chdir 't' if -d 't' }; + +### this is to make devel::cover happy ### +BEGIN { + use File::Spec; + require lib; + for (qw[../lib inc]) { + my $l = 'lib'; $l->import(File::Spec->rel2abs($_)) + } +} + +use strict; +use Test::More 'no_plan'; + +my $Class = 'CPANPLUS::Dist::Build::Constants'; + + +use_ok( $Class ); + +for my $name ( qw[BUILD BUILD_DIR] ) { + + my $sub = $Class->can( $name ); + ok( $sub, "$Class can $name" ); + ok( $sub->(), " $name called OK" ); +} diff --git a/lib/CPANPLUS/Dist/Build/t/02_CPANPLUS-Dist-Build.t b/lib/CPANPLUS/Dist/Build/t/02_CPANPLUS-Dist-Build.t new file mode 100644 index 0000000000..9417cece55 --- /dev/null +++ b/lib/CPANPLUS/Dist/Build/t/02_CPANPLUS-Dist-Build.t @@ -0,0 +1,235 @@ +### make sure we can find our conf.pl file +BEGIN { + use FindBin; + require "$FindBin::Bin/inc/conf.pl"; +} + +use strict; +use CPANPLUS::Configure; +use CPANPLUS::Backend; +use CPANPLUS::Internals::Constants; +use CPANPLUS::Module::Fake; +use CPANPLUS::Module::Author::Fake; + +use Config; +use Test::More 'no_plan'; +use File::Basename (); +use Data::Dumper; +use Config; +use IPC::Cmd 'can_run'; + +$SIG{__WARN__} = sub {warn @_ unless @_ && $_[0] =~ /redefined|isn't numeric/}; + +# Load these two modules in advance, even though they would be +# auto-loaded, because we want to override some of their subs. +use ExtUtils::Packlist; +use ExtUtils::Installed; + +my $Class = 'CPANPLUS::Dist::Build'; +my $Utils = 'CPANPLUS::Internals::Utils'; +my $Have_CC = can_run($Config{'cc'} )? 1 : 0; + + +my $Lib = File::Spec->rel2abs(File::Spec->catdir( qw[dummy-perl] )); +my $Src = File::Spec->rel2abs(File::Spec->catdir( qw[src] )); + +my $Verbose = @ARGV ? 1 : 0; +my $CB = CPANPLUS::Backend->new; +my $Conf = $CB->configure_object; + + +### create a fake object, so we don't use the actual module tree +my $Mod = CPANPLUS::Module::Fake->new( + module => 'Foo::Bar', + path => 'src', + author => CPANPLUS::Module::Author::Fake->new, + package => 'Foo-Bar-0.01.tar.gz', + ); + +$Conf->set_conf( base => 'dummy-cpanplus' ); +$Conf->set_conf( dist_type => '' ); +$Conf->set_conf( verbose => $Verbose ); +$Conf->set_conf( signature => 0 ); +### running tests will mess with the test output so skip 'm +$Conf->set_conf( skiptest => 1 ); + + # path, cc needed? +my %Map = ( noxs => 0, + xs => 1 + ); + + +### Disable certain possible settings, so we dont accidentally +### touch anything outside our sandbox +{ + ### set buildflags to install in our dummy perl dir + $Conf->set_conf( buildflags => "install_base=$Lib" ); + + ### don't start sending test reports now... ### + $CB->_callbacks->send_test_report( sub { 0 } ); + $Conf->set_conf( cpantest => 0 ); + + ### we dont need sudo -- we're installing in our own sandbox now + $Conf->set_program( sudo => undef ); +} + +use_ok( $Class ); + +ok( $Class->format_available, "Format is available" ); + + +while( my($path,$need_cc) = each %Map ) { + + my $mod = $Mod->clone; + ok( $mod, "Module object created for '$path'" ); + + ### set the fetch location -- it's local + { my $where = File::Spec->rel2abs( + File::Spec->catdir( $Src, $path, $mod->package ) + ); + + $mod->status->fetch( $where ); + + ok( -e $where, " Tarball '$where' exists" ); + } + + ok( $mod->prepare, " Preparing module" ); + + ok( $mod->status->dist_cpan, + " Dist registered as status" ); + + isa_ok( $mod->status->dist_cpan, $Class ); + + ok( $mod->status->dist_cpan->status->prepared, + " Prepared status registered" ); + is( $mod->status->dist_cpan->status->distdir, $mod->status->extract, + " Distdir status registered properly" ); + + + is( $mod->status->installer_type, INSTALLER_BUILD, + " Proper installer type found" ); + + + ### we might not have a C compiler + SKIP: { + skip("The CC compiler listed in Config.pm is not available " . + "-- skipping compile tests", 5) if $need_cc && !$Have_CC; + skip("Module::Build is not compiled with C support ". + "-- skipping compile tests", 5) + unless Module::Build->_mb_feature('C_support'); + + ok( $mod->create( ), "Creating module" ); + ok( $mod->status->dist_cpan->status->created, + " Created status registered" ); + + ### install tests + SKIP: { + skip("Install tests require Module::Build 0.2606 or higher", 2) + unless $Module::Build::VERSION >= '0.2606'; + + ### flush the lib cache + ### otherwise, cpanplus thinks the module's already installed + ### since the blib is already in @INC + $CB->_flush( list => [qw|lib|] ); + + ### force the install, make sure the Dist::Build->install() + ### sub gets called + ok( $mod->install( force => 1 ), + "Installing module" ); + ok( $mod->status->installed, + " Status says module installed" ); + } + + SKIP: { + my $minversion = 0.2609; + skip(qq[Uninstalling requires at least Module::Build $minversion], 1) + unless eval { Module::Build->VERSION($minversion); 1 }; + + # The installation directory actually needs to be in @INC + # in order to test uninstallation + 'lib'->import( File::Spec->catdir($Lib, 'lib', 'perl5') ); + + # EU::Installed and CP+::M are only capable of searching + # for modules in the core directories. We need to fake + # them out with our own subs here. + my $packlist = find_module($mod->name . '::.packlist'); + ok $packlist, "Found packlist"; + + my $p = ExtUtils::Packlist->new($packlist); + ok keys(%$p) > 0, "Packlist contains entries"; + + local *CPANPLUS::Module::installed_version = sub {1}; + local *CPANPLUS::Module::packlist = sub { [$p] }; + local *ExtUtils::Installed::files = sub { keys %$p }; + + ok( $mod->uninstall,"Uninstalling module" ); + } + } + + ### throw away all the extracted stuff + $Utils->_rmdir( dir => $Conf->get_conf('base') ); +} + +### test ENV setting while running Build.PL code +{ ### use print() not die() -- we're redirecting STDERR in tests! + my $env = 'ENV_CPANPLUS_IS_EXECUTING'; + my $clone = $Mod->clone; + + ok( $clone, 'Testing ENV settings $dist->prepare' ); + + $clone->status->fetch( File::Spec->catfile($Src, 'noxs', $clone->package) ); + ok( $clone->extract, ' Files extracted' ); + + ### write our own Build.PL file + my $build_pl = BUILD_PL->( $clone->status->extract ); + { my $fh = OPEN_FILE->( $build_pl, '>' ); + print $fh "die qq[ENV=\$ENV{$env}\n];"; + close $fh; + } + ok( -e $build_pl, " File exists" ); + + ### clear errors + CPANPLUS::Error->flush; + + ### since we're die'ing in the Build.PL, do a local *STDERR, + ### so we dont spam the result through the test -- this is expected + ### behaviour after all. + my $rv = do { local *STDERR; $clone->prepare( force => 1 ) }; + ok( !$rv, ' $mod->prepare failed' ); + + my $re = quotemeta( $build_pl ); + like( CPANPLUS::Error->stack_as_string, qr/ENV=$re/, + " \$ENV $env set correctly during execution"); + + ### and the ENV var should no longer be set now + ok( !$ENV{$env}, " ENV var now unset" ); +} + + +sub find_module { + my $module = shift; + + # Don't add the .pm yet, in case it's a packlist or something like ExtUtils::xsubpp. + my $file = File::Spec->catfile( split m/::/, $module ); + my $candidate; + foreach (@INC) { + if (-e ($candidate = File::Spec->catdir($_, $file)) + or + -e ($candidate = File::Spec->catdir($_, "$file.pm")) + or + -e ($candidate = File::Spec->catdir($_, 'auto', $file)) + or + -e ($candidate = File::Spec->catdir($_, 'auto', "$file.pm"))) { + return $candidate; + } + } + return; +} + + +# Local variables: +# c-indentation-style: bsd +# c-basic-offset: 4 +# indent-tabs-mode: nil +# End: +# vim: expandtab shiftwidth=4: diff --git a/lib/CPANPLUS/Dist/Build/t/inc/conf.pl b/lib/CPANPLUS/Dist/Build/t/inc/conf.pl new file mode 100644 index 0000000000..1892922f1d --- /dev/null +++ b/lib/CPANPLUS/Dist/Build/t/inc/conf.pl @@ -0,0 +1,201 @@ +### XXX copied from cpanplus's t/inc/conf.pl +BEGIN { + use FindBin; + use File::Spec; + + ### paths to our own 'lib' and 'inc' dirs + ### include them, relative from t/ + my @paths = map { "$FindBin::Bin/$_" } qw[../lib inc]; + + ### absolute'ify the paths in @INC; + my @rel2abs = map { File::Spec->rel2abs( $_ ) } + grep { not File::Spec->file_name_is_absolute( $_ ) } @INC; + + ### use require to make devel::cover happy + require lib; + for ( @paths, @rel2abs ) { + my $l = 'lib'; + $l->import( $_ ) + } + + use Config; + + ### and add them to the environment, so shellouts get them + $ENV{'PERL5LIB'} = join ':', + grep { defined } $ENV{'PERL5LIB'}, @paths, @rel2abs; + + ### add our own path to the front of $ENV{PATH}, so that cpanp-run-perl + ### and friends get picked up + $ENV{'PATH'} = join $Config{'path_sep'}, + grep { defined } "$FindBin::Bin/../../../bin", $ENV{'PATH'}; + + ### Fix up the path to perl, as we're about to chdir + ### but only under perlcore, or if the path contains delimiters, + ### meaning it's relative, but not looked up in your $PATH + $^X = File::Spec->rel2abs( $^X ) + if $ENV{PERL_CORE} or ( $^X =~ m|[/\\]| ); + + ### chdir to our own test dir, so we know all files are relative + ### to this point, no matter whether run from perlcore tests or + ### regular CPAN installs + chdir "$FindBin::Bin" if -d "$FindBin::Bin" +} + +BEGIN { + use IPC::Cmd; + + ### Win32 has issues with redirecting FD's properly in IPC::Run: + ### Can't redirect fd #4 on Win32 at IPC/Run.pm line 2801 + $IPC::Cmd::USE_IPC_RUN = 0 if $^O eq 'MSWin32'; + $IPC::Cmd::USE_IPC_RUN = 0 if $^O eq 'MSWin32'; +} + +use strict; +use CPANPLUS::Configure; + +use File::Path qw[rmtree]; +use FileHandle; +use File::Basename qw[basename]; + +{ ### Force the ignoring of .po files for L::M::S + $INC{'Locale::Maketext::Lexicon.pm'} = __FILE__; + $Locale::Maketext::Lexicon::VERSION = 0; +} + +1; + +__END__ + +# prereq has to be in our package file && core! +use constant TEST_CONF_PREREQ => 'Cwd'; +use constant TEST_CONF_MODULE => 'Foo::Bar::EU::NOXS'; +use constant TEST_CONF_INST_MODULE => 'Foo::Bar'; +use constant TEST_CONF_INVALID_MODULE => 'fnurk'; +use constant TEST_CONF_MIRROR_DIR => 'dummy-localmirror'; + +### we might need this Some Day when we're installing into +### our own sandbox. see t/20.t for details +# use constant TEST_INSTALL_DIR => do { +# my $dir = File::Spec->rel2abs( 'dummy-perl' ); +# +# ### clean up paths if we are on win32 +# ### dirs with spaces will be.. bad :( +# $^O eq 'MSWin32' +# ? Win32::GetShortPathName( $dir ) +# : $dir; +# }; + +# use constant TEST_INSTALL_DIR_LIB +# => File::Spec->catdir( TEST_INSTALL_DIR, 'lib' ); +# use constant TEST_INSTALL_DIR_BIN +# => File::Spec->catdir( TEST_INSTALL_DIR, 'bin' ); +# use constant TEST_INSTALL_DIR_MAN1 +# => File::Spec->catdir( TEST_INSTALL_DIR, 'man', 'man1' ); +# use constant TEST_INSTALL_DIR_MAN3 +# => File::Spec->catdir( TEST_INSTALL_DIR, 'man', 'man3' ); +# use constant TEST_INSTALL_DIR_ARCH +# => File::Spec->catdir( TEST_INSTALL_DIR, 'arch' ); +# +# use constant TEST_INSTALL_EU_MM_FLAGS => +# ' INSTALLDIRS=site' . +# ' INSTALLSITELIB=' . TEST_INSTALL_DIR_LIB . +# ' INSTALLSITEARCH=' . TEST_INSTALL_DIR_ARCH . # .packlist +# ' INSTALLARCHLIB=' . TEST_INSTALL_DIR_ARCH . # perllocal.pod +# ' INSTALLSITEBIN=' . TEST_INSTALL_DIR_BIN . +# ' INSTALLSCRIPT=' . TEST_INSTALL_DIR_BIN . +# ' INSTALLSITEMAN1DIR=' . TEST_INSTALL_DIR_MAN1 . +# ' INSTALLSITEMAN3DIR=' . TEST_INSTALL_DIR_MAN3; + + +sub gimme_conf { + my $conf = CPANPLUS::Configure->new(); + $conf->set_conf( hosts => [ { + path => 'dummy-CPAN', + scheme => 'file', + } ], + ); + $conf->set_conf( base => 'dummy-cpanplus' ); + $conf->set_conf( dist_type => '' ); + $conf->set_conf( signature => 0 ); + + _clean_test_dir( [ + $conf->get_conf('base'), + TEST_CONF_MIRROR_DIR, +# TEST_INSTALL_DIR_LIB, +# TEST_INSTALL_DIR_BIN, +# TEST_INSTALL_DIR_MAN1, +# TEST_INSTALL_DIR_MAN3, + ], 1 ); + + return $conf; +}; + +{ + my $fh; + my $file = ".".basename($0).".output"; + sub output_handle { + return $fh if $fh; + + $fh = FileHandle->new(">$file") + or warn "Could not open output file '$file': $!"; + + $fh->autoflush(1); + return $fh; + } + + sub output_file { return $file } +} + + +### clean these files if we're under perl core +END { + if ( $ENV{PERL_CORE} ) { + close output_handle(); 1 while unlink output_file(); + + _clean_test_dir( [ + gimme_conf->get_conf('base'), + TEST_CONF_MIRROR_DIR, + # TEST_INSTALL_DIR_LIB, + # TEST_INSTALL_DIR_BIN, + # TEST_INSTALL_DIR_MAN1, + # TEST_INSTALL_DIR_MAN3, + ], 1 ); + } +} + + + +### whenever we start a new script, we want to clean out our +### old files from the test '.cpanplus' dir.. +sub _clean_test_dir { + my $dirs = shift || []; + my $verbose = shift || 0; + + for my $dir ( @$dirs ) { + + my $dh; + opendir $dh, $dir or die "Could not open basedir '$dir': $!"; + while( my $file = readdir $dh ) { + next if $file =~ /^\./; # skip dot files + + my $path = File::Spec->catfile( $dir, $file ); + + ### directory, rmtree it + if( -d $path ) { + print "Deleting directory '$path'\n" if $verbose; + eval { rmtree( $path ) }; + warn "Could not delete '$path' while cleaning up '$dir'" if $@; + + ### regular file + } else { + print "Deleting file '$path'\n" if $verbose; + 1 while unlink $path; + } + } + + close $dh; + } + + return 1; +} +1; diff --git a/lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed b/lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed new file mode 100644 index 0000000000..e978261b98 --- /dev/null +++ b/lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed @@ -0,0 +1,35 @@ +######################################################################### +This is a binary file that was packed with the 'uupacktool.pl' which +is included in the Perl distribution. + +To unpack this file use the following command: + + uupacktool.pl -u lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz + +To recreate it use the following command: + + uupacktool.pl -p lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz lib/CPANPLUS/Dist/Build/t/src/noxs/Foo-Bar-0.01.tar.gz.packed + +Created at Fri May 4 14:00:53 2007 +######################################################################### +__UU__ +M'XL("-<X34(``T9O;RU"87(M,"XP,2YT87(`[9E;;]HP%,=Y]J<X+9722@-R +M!0G6:G2#M=)*I]+MI:M0((9&)#;*I5TU[;O/)J1JH!WK%-)U/;\7XTOXVSD^ +M]G'<Y;QR:`<5M:IJM=)F4`4-RY*IUK"T^VE*25-UW=`-T]`;)573+<,J@;6A +M_F2(P\@.`$I3F]'?M5M7GPXD35\(W?OV?W_4[GWL]//6D.^C;IJ/VU\WE^QO +M6*(("GF)K]S^QV#[8,/"\C!V/4J>NT](<63\_Z3=.^YV^N<Y:ZSS?TVM+_M_ +MO6Z@_Q?!PO')6:?]X:1##F/7<ZJ?/Y%T*I"(AE%UYA'/'=;$9*F)R5*=^63H +MLMJ8\Z',>;ABO%@R_K^P==X::_?_NK;D_Z9JUM'_BZ"\58O#H";=>48#CY!9 +MX+((MK5J5?O&MEMIGD\AR3]WAY%<R?B_7.,WH/'T\Y]AJ`:>_XI@Q?YRC\]9 +MX^GV-U6]CO8O@@?MG\1XN6FLV?^EZ9?L;ZD8_Q<#V;^BMJ-!KRVB?]*W_9E' +MH0)B%H`([R%,"L1/DK9L?SD_.CV[:WMB,WB;-'M'O\_3ZHC[!W?M3\^/.J+Y +M-0U"ES/P7$9#<!G,N`/A%8\]!X84W`GC`76:9.=KYZQ_?-J#?5`8CX!/FS`1 +M:?K\.."^?'9+:0F)41R)D,4>3>T)E9UN-L7<%15E6-$3G?(IB\*'1,OP1[*+ +MOYA+ER&Z<L4?AR*E&36XH4I`P>-\ZK*)>'=!9E2[BO0U9>]"O6P!E.]UAU[; +MGN(0\H/(\AN:5G'FW4)D3^E<:NP&8905'/.8B5&`T!S9GDCAT?&$=,29LSPL +MA\HQ_21$PP#O=9%9_^4I8`,:?Q7_X??_0EBQ_]U'G?PTUI[_36MY_]<T#??_ +M(GCL_)],`W'>QRWAOR;C_^G7WYPUUOF_;C16SG\&QO^%$(<BA.=.[%$1.DOS +MMX`0_Q9VAC(CHD=&;[(-1'BY*T-,?UXX8+9/8?\`E#3Z5M[(VG`4N+-H(.\3 +M0UE]`4IF=U'@4K;;:Y%$J7(P"J@=T<$\-T@>QY5GXV3\/[D$REUC[?ZO&\OQ +MGZFIZ/]%L+C_3RR/U_\(@B`(@B`(@B`(@B`(@B`(@B`(@B`(@B`(@B`(@B`( +,\D_S"QCQWFL`4``` diff --git a/lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed b/lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed new file mode 100644 index 0000000000..52e09e2a17 --- /dev/null +++ b/lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed @@ -0,0 +1,50 @@ +######################################################################### +This is a binary file that was packed with the 'uupacktool.pl' which +is included in the Perl distribution. + +To unpack this file use the following command: + + uupacktool.pl -u lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz + +To recreate it use the following command: + + uupacktool.pl -p lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz lib/CPANPLUS/Dist/Build/t/src/xs/Foo-Bar-0.01.tar.gz.packed + +Created at Fri May 4 14:00:54 2007 +######################################################################### +__UU__ +M'XL("-\X34(``T9O;RU"87(M,"XP,2YT87(`[5K_3QI)%/=7YZ]XU39H(BN[ +M@%PP-*)N+:D"$6QL[AHRP``;EQVZ.RN2IO][WV,79+4]KG>(UW8^B<+NO)GW +MYGV;F3>\D3)]S/UTQLB8^QM/@PRBD,_3IUG(FXN?,VR8&<O*6ME<-EO8R)A6 +M/GNP`?DGDB>!,%#<!]BXX9[X.[IE[;.)S#Y_$KQ9M/_)VW+US&ZLF@?IXR"7 +M^[[]K=P#^V?S5GX#UJ+$W]S^%>!#X!!;'GJ.*]ASRZ2Q/B3B_Z)<K;RQ&\T5 +M\U@6_R8F^P?Q?W!0T/&_#K0=;[\G99O[QLAEQZ'C=HWZ.8OS`7.=]CZZR/XQ +MM0\3CW<!F_D+N[3+IQ<V4R)0-,R%W2P;DZ&K,\G_'HGX)_,^`8\?W_]ELYF< +MWO^M`X_L3^&]8AX_;O]<QBIH^Z\#W[1_E-Y7QF/9^F\5'J[_>=/,Z?5_'=AV +MO(X;=@5LV==-^[)J#+;8_;N1\-WDF^O&U3&]81>UTZMS&TJ`#E,LHL=L;M;+ +M)^_*9\EWC#F>8D[0$K?"VW&\4:AVV2:^`\+TF=&WD]JI762;EW;S??D<1XA( +MX1584"I!9O=P2E6[:M:OFG,ZO;_XS_AN_(^&*^.Q)/XM*U=X&/\93`DZ_M>` +M$>_<\+Z81^PA"P,!@?*=CCIDS!>?0L<78-^-I*\$-L_>G$X\?BYY5\1=;KD? +MP*?Q#AQ5&F5X95_7:Y?-5K-\UH"C^*'V;O857KZW+QN56A4PL-FT1XDZS]@L +MC$X$VU!18AB`DB"F%)@X\'N'NZY`KAX?B@`G(J`]@:[H\=!5!E2E$D7H2O"D +MBKOA0%/:!3H8.VH@,=-PN!7^!/I2=L$7/)">`5<XKWO9'2]0@G<-'.4T&C5P +MAB-W,I,)I8&)#'T8A6W7Z4`O]#K*D5ZP/Q3(HQOL=_!!<4\%!LVI.7`"ZB3' +M`4K3<;G/B7R3M'G=:.)9"E)%;$\=D@)Z-/9L.IX075#8?P^&\M;Q^O3@]7$< +M-$U'H4Q3!<VT+?T%&^!@8P=%#?BM@*$82G]BL(2],/M"BAA#Z37\279AFVP7 +M/D;&NE<(T1U]AI<+?3]/^WV!+XNTD6UIC$,V-WP)4I1S<'*L+:5"C^.CN1?. +M_6-J_+HO7/*%+L2*1"O!0/@"U6@>LE;+KIZV6DAXC'1C0*T&*FRCKCKA4'AJ +MJE7HH1*FUAG*;N@*`SZ@.MM"D;>)KH,NI5XP5AJ@A4VHEO$TRV(CI*&.RR!: +M60DOF`W5=OG@_M^\8^-#M59O5!H,EZM[.]+B]9T.IW;CY+)2;^)<&6M\6^QH +MD#WHH%\JU`)Z[\"Z"PR,"G"EO`G`=6X$NH!@/$0%^2![]+0@\IACF(B^Z_1Q +M9!">#/L#"B=7D!<0+:F,A1ZI0J"/L^.$O$8LL!7'`V-5Z27BC<VG5+YJOJU= +M,E;&`#+0RZ6_!]P(#9+LB!M][O*[B='#-8;^^)A/[M5GVU`^;]08HYW'CKE+ +M?#NX17CN-/G+(EG_B^LV*^:Q;/^?S68?G?\R>OU?"]+I-&Q_*%^<%TTCPVAU +M+$+L$PP71$H>12#GB%-+$5-9&GX@M'F;4GM'%?]!%L55$QM1@M"[\>388YBM +MA$\YK]6>%.%BFKEQ@:`J)<32H7#60>8/<V^^DM-LJ%CYW*K]*9"(_[A^NVH> +MR^(_4WA<_\GI^O]:<&R?5:KP&48^G<FW3,,P__*VX$NTJ;\_%<3M\@:H_5`' +MUR^"1/S39=`3\/A7]7_]^X^UX)']YY>!J^.Q-/_G\H_KOY;._^O`]HO],/"G +MEJ<C%YLE^L@-,-'3"?NYA=1X,B3B/[K&7SF/I?%O/3S_97.FJ>-_'8A__Q59 +M7O_\Z[=#(OYG/_]9,8]E\6_EOG'_K^L_:P&=\A)U%5SQAQ-XV9[66$K@B?&# +MN@L#V*'+V*B.WJ*2$=T3I&9GQ=0>M08=WQFI%B64(+I&2"5VERGX2'1T2Q#Q +M2K^.RMNMZ5,KZJ^W'AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH: ++R_`55?+KB0!0```` |