use strict;
use warnings;
package inc::latest;
# ABSTRACT: use modules bundled in inc/ if they are newer than installed ones
our $VERSION = '0.500';
package inc::latest;
use Carp;
use File::Basename ();
use File::Spec ();
use File::Path ();
use IO::File ();
use File::Copy ();
# track and return modules loaded by inc::latest
my @loaded_modules;
sub loaded_modules { @loaded_modules }
# must ultimately "goto" the import routine of the module to be loaded
# so that the calling package is correct when $mod->import() runs.
sub import {
my ( $package, $mod, @args ) = @_;
return unless ( defined $mod );
my $private_path = 'inc/latest/private.pm';
if ( -e $private_path ) {
# user mode - delegate work to bundled private module
require $private_path;
splice( @_, 0, 1, 'inc::latest::private' );
goto \&inc::latest::private::import;
}
# author mode - just record and load the modules
push( @loaded_modules, $mod );
require inc::latest::private;
goto \&inc::latest::private::_load_module;
}
sub write {
my $package = shift;
my ( $where, @preload ) = @_;
warn "should really be writing in inc/" unless $where =~ /inc$/;
# write inc/latest.pm
File::Path::mkpath($where);
my $fh = IO::File->new( File::Spec->catfile( $where, 'latest.pm' ), "w" );
print {$fh} "# This stub created by inc::latest $VERSION\n";
print {$fh} <<'HERE';
package inc::latest;
use strict;
use vars '@ISA';
require inc::latest::private;
@ISA = qw/inc::latest::private/;
HERE
if (@preload) {
print {$fh} "\npackage inc::latest::preload;\n";
for my $mod (@preload) {
print {$fh} "inc::latest->import('$mod');\n";
}
}
print {$fh} "\n1;\n";
close $fh;
# write inc/latest/private;
require inc::latest::private;
File::Path::mkpath( File::Spec->catdir( $where, 'latest' ) );
my $from = $INC{'inc/latest/private.pm'};
my $to = File::Spec->catfile( $where, 'latest', 'private.pm' );
File::Copy::copy( $from, $to ) or die "Couldn't copy '$from' to '$to': $!";
return 1;
}
sub bundle_module {
my ( $package, $module, $where ) = @_;
# create inc/inc_$foo
( my $dist = $module ) =~ s{::}{-}g;
my $inc_lib = File::Spec->catdir( $where, "inc_$dist" );
File::Path::mkpath $inc_lib;
# get list of files to copy
require ExtUtils::Installed;
# workaround buggy EU::Installed check of @INC
my $inst = ExtUtils::Installed->new( extra_libs => [@INC] );
my $packlist = $inst->packlist($module) or die "Couldn't find packlist";
my @files = grep { /\.pm$/ } keys %$packlist;
# figure out prefix
my $mod_path = quotemeta $package->_mod2path($module);
my ($prefix) = grep { /$mod_path$/ } @files;
$prefix =~ s{$mod_path$}{};
# copy files
for my $from (@files) {
next unless $from =~ /\.pm$/;
( my $mod_path = $from ) =~ s{^\Q$prefix\E}{};
my $to = File::Spec->catfile( $inc_lib, $mod_path );
File::Path::mkpath( File::Basename::dirname($to) );
File::Copy::copy( $from, $to ) or die "Couldn't copy '$from' to '$to': $!";
}
return 1;
}
# Translate a module name into a directory/file.pm to search for in @INC
sub _mod2path {
my ( $self, $mod ) = @_;
my @parts = split /::/, $mod;
$parts[-1] .= '.pm';
return $parts[0] if @parts == 1;
return File::Spec->catfile(@parts);
}
1;
# vim: ts=4 sts=4 sw=4 tw=75 et:
__END__
=pod
=encoding UTF-8
=head1 NAME
inc::latest - use modules bundled in inc/ if they are newer than installed ones
=head1 VERSION
version 0.500
=head1 SYNOPSIS
# in Makefile.PL or Build.PL
use inc::latest 'Some::Configure::Prereq';
=head1 DESCRIPTION
B. It was originally bundled
(as an experiment) with L and has been split out for more
general use.
The C module helps bootstrap configure-time dependencies for
CPAN distributions. These dependencies get bundled into the C
directory within a distribution and are used by F or F.
Arguments to C are module names that are checked against both
the current C<@INC> array and against specially-named directories in
C. If the bundled version is newer than the installed one (or the
module isn't installed, then, the bundled directory is added to the start
of C<@INC> and the module is loaded from there.
There are actually two variations of C -- one for authors and
one for the C directory. For distribution authors, the C
installed in the system will record modules loaded via C and
can be used to create the bundled files in C, including writing the
second variation as C.
This second C is the one that is loaded in a distribution
being installed (e.g. from F or F). This bundled
C is the one that determines which module to load.
=head2 Special notes on bundling
The C module creates bundled directories based on the packlist
file of an installed distribution. Even though C takes module
name arguments, it is better to think of it as bundling and making
available entire I. When a module is loaded through
C, it looks in all bundled distributions in C for a
newer module than can be found in the existing C<@INC> array.
Thus, the module-name provided should usually be the "top-level" module
name of a distribution, though this is not strictly required.
C has a number of heuristics to discover module names,
allowing users to do things like this:
use inc::latest 'Devel::AssertOS::Unix';
even though Devel::AssertOS::Unix is contained within the Devel-CheckOS
distribution.
At the current time, packlists are required. Thus, bundling dual-core
modules may require a 'forced install' over versions in the latest version
of perl in order to create the necessary packlist for bundling.
=head2 Managing dependency chains
Before bundling a distribution you must ensure that all prerequisites are
also bundled and load in the correct order.
For example, if you need C, but C depends on C,
and you have bundled C, your F might look like this:
use inc::latest 'Wobble';
use inc::latest 'Wibble';
use inc::latest 'Module::Build';
Module::Build->new(
module_name => 'Foo::Bar',
license => 'perl',
)->create_build_script;
Authors are strongly suggested to limit the bundling of additional
dependencies if at all possible and to carefully test their distribution
tarballs before uploading to CPAN.
=head1 USAGE
=head2 As bundled in inc/
Using L, a special stub module will be created in your
distribute directory as F. In your F or
F, you can then load C to load bundled modules.
When calling C