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, the bundled C takes a single module name and optional arguments to pass to that module's own import method. use inc::latest 'Foo::Bar' qw/foo bar baz/; The implementation is private. Only the C method is public. =head2 Author-mode When you have L installed from CPAN, then you are in author-mode if any of the Author-mode methods are available. For example: if ( inc::latest->can('write') ) { inc::latest->write('inc'); } Using author-mode, you can create the stub F and bundle modules into F. =over 4 =item loaded_modules() my @list = inc::latest->loaded_modules; This takes no arguments and always returns a list of module names requested for loading via "use inc::latest 'MODULE'", regardless of whether the load was successful or not. =item write() inc::latest->write( 'inc' ); This writes the bundled version of inc::latest to the directory name given as an argument. It almost all cases, it should be 'C'. =item bundle_module() for my $mod ( inc::latest->loaded_modules ) { inc::latest->bundle_module($mod, $dir); } If $mod corresponds to a packlist, then this function creates a specially-named directory in $dir and copies all .pm files from the modlist to the new directory (which almost always should just be 'inc'). For example, if Foo::Bar is the name of the module, and $dir is 'inc', then the directory would be 'inc/inc_Foo-Bar' and contain files like this: inc/inc_Foo-Bar/Foo/Bar.pm Currently, $mod B have a packlist. If this is not the case (e.g. for a dual-core module), then the bundling will fail. You may be able to create a packlist by forced installing the module on top of the version that came with core Perl. =back =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan =head1 SUPPORT =head2 Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at L. You will be notified automatically of any progress on your issue. =head2 Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. L git clone https://github.com/dagolden/inc-latest.git =head1 AUTHORS =over 4 =item * David Golden =item * Eric Wilhelm =back =head1 COPYRIGHT AND LICENSE This software is Copyright (c) 2009 by David Golden. This is free software, licensed under: The Apache License, Version 2.0, January 2004 =cut