Blame lib/inc/latest.pm

Packit 4fdfb4
use strict;
Packit 4fdfb4
use warnings;
Packit 4fdfb4
Packit 4fdfb4
package inc::latest;
Packit 4fdfb4
Packit 4fdfb4
# ABSTRACT: use modules bundled in inc/ if they are newer than installed ones
Packit 4fdfb4
Packit 4fdfb4
our $VERSION = '0.500';
Packit 4fdfb4
Packit 4fdfb4
package inc::latest;
Packit 4fdfb4
Packit 4fdfb4
use Carp;
Packit 4fdfb4
use File::Basename ();
Packit 4fdfb4
use File::Spec     ();
Packit 4fdfb4
use File::Path     ();
Packit 4fdfb4
use IO::File       ();
Packit 4fdfb4
use File::Copy     ();
Packit 4fdfb4
Packit 4fdfb4
# track and return modules loaded by inc::latest
Packit 4fdfb4
my @loaded_modules;
Packit 4fdfb4
sub loaded_modules { @loaded_modules }
Packit 4fdfb4
Packit 4fdfb4
# must ultimately "goto" the import routine of the module to be loaded
Packit 4fdfb4
# so that the calling package is correct when $mod->import() runs.
Packit 4fdfb4
sub import {
Packit 4fdfb4
    my ( $package, $mod, @args ) = @_;
Packit 4fdfb4
    return unless ( defined $mod );
Packit 4fdfb4
Packit 4fdfb4
    my $private_path = 'inc/latest/private.pm';
Packit 4fdfb4
    if ( -e $private_path ) {
Packit 4fdfb4
        # user mode - delegate work to bundled private module
Packit 4fdfb4
        require $private_path;
Packit 4fdfb4
        splice( @_, 0, 1, 'inc::latest::private' );
Packit 4fdfb4
        goto \&inc::latest::private::import;
Packit 4fdfb4
    }
Packit 4fdfb4
Packit 4fdfb4
    # author mode - just record and load the modules
Packit 4fdfb4
    push( @loaded_modules, $mod );
Packit 4fdfb4
    require inc::latest::private;
Packit 4fdfb4
    goto \&inc::latest::private::_load_module;
Packit 4fdfb4
}
Packit 4fdfb4
Packit 4fdfb4
sub write {
Packit 4fdfb4
    my $package = shift;
Packit 4fdfb4
    my ( $where, @preload ) = @_;
Packit 4fdfb4
Packit 4fdfb4
    warn "should really be writing in inc/" unless $where =~ /inc$/;
Packit 4fdfb4
Packit 4fdfb4
    # write inc/latest.pm
Packit 4fdfb4
    File::Path::mkpath($where);
Packit 4fdfb4
    my $fh = IO::File->new( File::Spec->catfile( $where, 'latest.pm' ), "w" );
Packit 4fdfb4
    print {$fh} "# This stub created by inc::latest $VERSION\n";
Packit 4fdfb4
    print {$fh} <<'HERE';
Packit 4fdfb4
package inc::latest;
Packit 4fdfb4
use strict;
Packit 4fdfb4
use vars '@ISA';
Packit 4fdfb4
require inc::latest::private;
Packit 4fdfb4
@ISA = qw/inc::latest::private/;
Packit 4fdfb4
HERE
Packit 4fdfb4
    if (@preload) {
Packit 4fdfb4
        print {$fh} "\npackage inc::latest::preload;\n";
Packit 4fdfb4
        for my $mod (@preload) {
Packit 4fdfb4
            print {$fh} "inc::latest->import('$mod');\n";
Packit 4fdfb4
        }
Packit 4fdfb4
    }
Packit 4fdfb4
    print {$fh} "\n1;\n";
Packit 4fdfb4
    close $fh;
Packit 4fdfb4
Packit 4fdfb4
    # write inc/latest/private;
Packit 4fdfb4
    require inc::latest::private;
Packit 4fdfb4
    File::Path::mkpath( File::Spec->catdir( $where, 'latest' ) );
Packit 4fdfb4
    my $from = $INC{'inc/latest/private.pm'};
Packit 4fdfb4
    my $to = File::Spec->catfile( $where, 'latest', 'private.pm' );
Packit 4fdfb4
    File::Copy::copy( $from, $to ) or die "Couldn't copy '$from' to '$to': $!";
Packit 4fdfb4
Packit 4fdfb4
    return 1;
Packit 4fdfb4
}
Packit 4fdfb4
Packit 4fdfb4
sub bundle_module {
Packit 4fdfb4
    my ( $package, $module, $where ) = @_;
Packit 4fdfb4
Packit 4fdfb4
    # create inc/inc_$foo
Packit 4fdfb4
    ( my $dist = $module ) =~ s{::}{-}g;
Packit 4fdfb4
    my $inc_lib = File::Spec->catdir( $where, "inc_$dist" );
Packit 4fdfb4
    File::Path::mkpath $inc_lib;
Packit 4fdfb4
Packit 4fdfb4
    # get list of files to copy
Packit 4fdfb4
    require ExtUtils::Installed;
Packit 4fdfb4
    # workaround buggy EU::Installed check of @INC
Packit 4fdfb4
    my $inst = ExtUtils::Installed->new( extra_libs => [@INC] );
Packit 4fdfb4
    my $packlist = $inst->packlist($module) or die "Couldn't find packlist";
Packit 4fdfb4
    my @files = grep { /\.pm$/ } keys %$packlist;
Packit 4fdfb4
Packit 4fdfb4
    # figure out prefix
Packit 4fdfb4
    my $mod_path = quotemeta $package->_mod2path($module);
Packit 4fdfb4
    my ($prefix) = grep { /$mod_path$/ } @files;
Packit 4fdfb4
    $prefix =~ s{$mod_path$}{};
Packit 4fdfb4
Packit 4fdfb4
    # copy files
Packit 4fdfb4
    for my $from (@files) {
Packit 4fdfb4
        next unless $from =~ /\.pm$/;
Packit 4fdfb4
        ( my $mod_path = $from ) =~ s{^\Q$prefix\E}{};
Packit 4fdfb4
        my $to = File::Spec->catfile( $inc_lib, $mod_path );
Packit 4fdfb4
        File::Path::mkpath( File::Basename::dirname($to) );
Packit 4fdfb4
        File::Copy::copy( $from, $to ) or die "Couldn't copy '$from' to '$to': $!";
Packit 4fdfb4
    }
Packit 4fdfb4
    return 1;
Packit 4fdfb4
}
Packit 4fdfb4
Packit 4fdfb4
# Translate a module name into a directory/file.pm to search for in @INC
Packit 4fdfb4
sub _mod2path {
Packit 4fdfb4
    my ( $self, $mod ) = @_;
Packit 4fdfb4
    my @parts = split /::/, $mod;
Packit 4fdfb4
    $parts[-1] .= '.pm';
Packit 4fdfb4
    return $parts[0] if @parts == 1;
Packit 4fdfb4
    return File::Spec->catfile(@parts);
Packit 4fdfb4
}
Packit 4fdfb4
Packit 4fdfb4
1;
Packit 4fdfb4
Packit 4fdfb4
Packit 4fdfb4
# vim: ts=4 sts=4 sw=4 tw=75 et:
Packit 4fdfb4
Packit 4fdfb4
__END__
Packit 4fdfb4
Packit 4fdfb4
=pod
Packit 4fdfb4
Packit 4fdfb4
=encoding UTF-8
Packit 4fdfb4
Packit 4fdfb4
=head1 NAME
Packit 4fdfb4
Packit 4fdfb4
inc::latest - use modules bundled in inc/ if they are newer than installed ones
Packit 4fdfb4
Packit 4fdfb4
=head1 VERSION
Packit 4fdfb4
Packit 4fdfb4
version 0.500
Packit 4fdfb4
Packit 4fdfb4
=head1 SYNOPSIS
Packit 4fdfb4
Packit 4fdfb4
  # in Makefile.PL or Build.PL
Packit 4fdfb4
  use inc::latest 'Some::Configure::Prereq';
Packit 4fdfb4
Packit 4fdfb4
=head1 DESCRIPTION
Packit 4fdfb4
Packit 4fdfb4
B<WARNING -- THIS IS AN EXPERIMENTAL MODULE>.  It was originally bundled
Packit 4fdfb4
(as an experiment) with L<Module::Build> and has been split out for more
Packit 4fdfb4
general use.
Packit 4fdfb4
Packit 4fdfb4
The C<inc::latest> module helps bootstrap configure-time dependencies for
Packit 4fdfb4
CPAN distributions.  These dependencies get bundled into the C<inc>
Packit 4fdfb4
directory within a distribution and are used by F<Makefile.PL> or F<Build.PL>.
Packit 4fdfb4
Packit 4fdfb4
Arguments to C<inc::latest> are module names that are checked against both
Packit 4fdfb4
the current C<@INC> array and against specially-named directories in
Packit 4fdfb4
C<inc>.  If the bundled version is newer than the installed one (or the
Packit 4fdfb4
module isn't installed, then, the bundled directory is added to the start
Packit 4fdfb4
of C<@INC> and the module is loaded from there.
Packit 4fdfb4
Packit 4fdfb4
There are actually two variations of C<inc::latest> -- one for authors and
Packit 4fdfb4
one for the C<inc> directory.  For distribution authors, the C<inc::latest>
Packit 4fdfb4
installed in the system will record modules loaded via C<inc::latest> and
Packit 4fdfb4
can be used to create the bundled files in C<inc>, including writing the
Packit 4fdfb4
second variation as C<inc/latest.pm>.
Packit 4fdfb4
Packit 4fdfb4
This second C<inc::latest> is the one that is loaded in a distribution
Packit 4fdfb4
being installed (e.g. from F<Makefile.PL> or F<Build.PL>).  This bundled
Packit 4fdfb4
C<inc::latest> is the one that determines which module to load.
Packit 4fdfb4
Packit 4fdfb4
=head2 Special notes on bundling
Packit 4fdfb4
Packit 4fdfb4
The C<inc::latest> module creates bundled directories based on the packlist
Packit 4fdfb4
file of an installed distribution.  Even though C<inc::latest> takes module
Packit 4fdfb4
name arguments, it is better to think of it as bundling and making
Packit 4fdfb4
available entire I<distributions>.  When a module is loaded through
Packit 4fdfb4
C<inc::latest>, it looks in all bundled distributions in C<inc/> for a
Packit 4fdfb4
newer module than can be found in the existing C<@INC> array.
Packit 4fdfb4
Packit 4fdfb4
Thus, the module-name provided should usually be the "top-level" module
Packit 4fdfb4
name of a distribution, though this is not strictly required.
Packit 4fdfb4
C<inc::latest> has a number of heuristics to discover module names,
Packit 4fdfb4
allowing users to do things like this:
Packit 4fdfb4
Packit 4fdfb4
  use inc::latest 'Devel::AssertOS::Unix';
Packit 4fdfb4
Packit 4fdfb4
even though Devel::AssertOS::Unix is contained within the Devel-CheckOS
Packit 4fdfb4
distribution.
Packit 4fdfb4
Packit 4fdfb4
At the current time, packlists are required.  Thus, bundling dual-core
Packit 4fdfb4
modules may require a 'forced install' over versions in the latest version
Packit 4fdfb4
of perl in order to create the necessary packlist for bundling.
Packit 4fdfb4
Packit 4fdfb4
=head2 Managing dependency chains
Packit 4fdfb4
Packit 4fdfb4
Before bundling a distribution you must ensure that all prerequisites are
Packit 4fdfb4
also bundled and load in the correct order.
Packit 4fdfb4
Packit 4fdfb4
For example, if you need C<Wibble>, but C<Wibble> depends on C<Wobble>,
Packit 4fdfb4
and you have bundled C<Module::Build>, your F<Build.PL> might look like this:
Packit 4fdfb4
Packit 4fdfb4
  use inc::latest 'Wobble';
Packit 4fdfb4
  use inc::latest 'Wibble';
Packit 4fdfb4
  use inc::latest 'Module::Build';
Packit 4fdfb4
Packit 4fdfb4
  Module::Build->new(
Packit 4fdfb4
    module_name => 'Foo::Bar',
Packit 4fdfb4
    license => 'perl',
Packit 4fdfb4
  )->create_build_script;
Packit 4fdfb4
Packit 4fdfb4
Authors are strongly suggested to limit the bundling of additional
Packit 4fdfb4
dependencies if at all possible and to carefully test their distribution
Packit 4fdfb4
tarballs before uploading to CPAN.
Packit 4fdfb4
Packit 4fdfb4
=head1 USAGE
Packit 4fdfb4
Packit 4fdfb4
=head2 As bundled in inc/
Packit 4fdfb4
Packit 4fdfb4
Using L</Author-mode>, a special stub module will be created in your
Packit 4fdfb4
distribute directory as F<inc/latest.pm>.  In your F<Makefile.PL> or
Packit 4fdfb4
F<Build.PL>, you can then load C<inc::latest> to load bundled modules.
Packit 4fdfb4
Packit 4fdfb4
When calling C<use>, the bundled C<inc::latest> takes a single module name
Packit 4fdfb4
and optional arguments to pass to that module's own import method.
Packit 4fdfb4
Packit 4fdfb4
  use inc::latest 'Foo::Bar' qw/foo bar baz/;
Packit 4fdfb4
Packit 4fdfb4
The implementation is private.  Only the C<import> method is public.
Packit 4fdfb4
Packit 4fdfb4
=head2 Author-mode
Packit 4fdfb4
Packit 4fdfb4
When you have L<inc::latest> installed from CPAN, then you are in author-mode
Packit 4fdfb4
if any of the Author-mode methods are available.  For example:
Packit 4fdfb4
Packit 4fdfb4
  if ( inc::latest->can('write') ) {
Packit 4fdfb4
    inc::latest->write('inc');
Packit 4fdfb4
  }
Packit 4fdfb4
Packit 4fdfb4
Using author-mode, you can create the stub F<inc/latest.pm> and bundle
Packit 4fdfb4
modules into F<inc>.
Packit 4fdfb4
Packit 4fdfb4
=over 4
Packit 4fdfb4
Packit 4fdfb4
=item loaded_modules()
Packit 4fdfb4
Packit 4fdfb4
  my @list = inc::latest->loaded_modules;
Packit 4fdfb4
Packit 4fdfb4
This takes no arguments and always returns a list of module names requested
Packit 4fdfb4
for loading via "use inc::latest 'MODULE'", regardless of whether the load
Packit 4fdfb4
was successful or not.
Packit 4fdfb4
Packit 4fdfb4
=item write()
Packit 4fdfb4
Packit 4fdfb4
  inc::latest->write( 'inc' );
Packit 4fdfb4
Packit 4fdfb4
This writes the bundled version of inc::latest to the directory name given
Packit 4fdfb4
as an argument.  It almost all cases, it should be 'C<inc>'.
Packit 4fdfb4
Packit 4fdfb4
=item bundle_module()
Packit 4fdfb4
Packit 4fdfb4
  for my $mod ( inc::latest->loaded_modules ) {
Packit 4fdfb4
    inc::latest->bundle_module($mod, $dir);
Packit 4fdfb4
  }
Packit 4fdfb4
Packit 4fdfb4
If $mod corresponds to a packlist, then this function creates a
Packit 4fdfb4
specially-named directory in $dir and copies all .pm files from the modlist
Packit 4fdfb4
to the new directory (which almost always should just be 'inc').  For
Packit 4fdfb4
example, if Foo::Bar is the name of the module, and $dir is 'inc', then the
Packit 4fdfb4
directory would be 'inc/inc_Foo-Bar' and contain files like this:
Packit 4fdfb4
Packit 4fdfb4
  inc/inc_Foo-Bar/Foo/Bar.pm
Packit 4fdfb4
Packit 4fdfb4
Currently, $mod B<must> have a packlist.  If this is not the case (e.g. for
Packit 4fdfb4
a dual-core module), then the bundling will fail.  You may be able to
Packit 4fdfb4
create a packlist by forced installing the module on top of the version
Packit 4fdfb4
that came with core Perl.
Packit 4fdfb4
Packit 4fdfb4
=back
Packit 4fdfb4
Packit 4fdfb4
=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
Packit 4fdfb4
Packit 4fdfb4
=head1 SUPPORT
Packit 4fdfb4
Packit 4fdfb4
=head2 Bugs / Feature Requests
Packit 4fdfb4
Packit 4fdfb4
Please report any bugs or feature requests through the issue tracker
Packit 4fdfb4
at L<https://github.com/dagolden/inc-latest/issues>.
Packit 4fdfb4
You will be notified automatically of any progress on your issue.
Packit 4fdfb4
Packit 4fdfb4
=head2 Source Code
Packit 4fdfb4
Packit 4fdfb4
This is open source software.  The code repository is available for
Packit 4fdfb4
public review and contribution under the terms of the license.
Packit 4fdfb4
Packit 4fdfb4
L<https://github.com/dagolden/inc-latest>
Packit 4fdfb4
Packit 4fdfb4
  git clone https://github.com/dagolden/inc-latest.git
Packit 4fdfb4
Packit 4fdfb4
=head1 AUTHORS
Packit 4fdfb4
Packit 4fdfb4
=over 4
Packit 4fdfb4
Packit 4fdfb4
=item *
Packit 4fdfb4
Packit 4fdfb4
David Golden <dagolden@cpan.org>
Packit 4fdfb4
Packit 4fdfb4
=item *
Packit 4fdfb4
Packit 4fdfb4
Eric Wilhelm <ewilhelm@cpan.org>
Packit 4fdfb4
Packit 4fdfb4
=back
Packit 4fdfb4
Packit 4fdfb4
=head1 COPYRIGHT AND LICENSE
Packit 4fdfb4
Packit 4fdfb4
This software is Copyright (c) 2009 by David Golden.
Packit 4fdfb4
Packit 4fdfb4
This is free software, licensed under:
Packit 4fdfb4
Packit 4fdfb4
  The Apache License, Version 2.0, January 2004
Packit 4fdfb4
Packit 4fdfb4
=cut