I've been preparing for the imminent breakup of libwin32 by compiling
the IPC modules into a Module::Build-based distribution. In the
process, I wound up making a number of changes to my Module::Build
subclass to simplify my work.
As of right now it:
- Inserts the distribution's version number into the README
- Makes sure that version number appears in Changes
- Updates the =head1 VERSION section in each module with
the module's version number, the release date (from Changes)
and the distribution name & version (if multiple modules).
I've thought of but haven't yet implemented:
- Copy what's new in this release from Changes to README
- Verify that the release date seems valid
As I was about to check in these changes, I realized that's the wrong
way to do it. I want these features in all my modules, and I don't want
to have to copy & paste them into My_Build subclasses. And I'll bet
other people would find it useful.
So my question is how I should go about distributing this. One comment:
I would like people who run ./Build distdir (or any target depending on
it) and don't have this extension installed to get a warning about it.
(It would be acceptable if only future versions of Module::Build
produced the warning.)
Some possibilities:
1. Try to get it integrated into Module::Build, with some flag in
Build.PL to activate it.
Advantages: Everybody gets the features.
Disadvantages: It'd be harder for me to update it. Most people just
install modules and don't need these features.
2. Get Module::Build to look for my class and use it if found.
Advantages: Updating my extension doesn't require a Module::Build release.
Disadvantages: Old versions of Module::Build won't warn if my extension
isn't installed. May be tricky to get working right. Other people may
want a different extension instead.
3. Release it as a separate module that subclasses Module::Build.
Change my distribution to use that module if available, otherwise fall
back to Module::Build (and wrap ACTION_distdir with my warning message
about missing functionality).
Advantages: Doesn't require any changes to Module::Build. If other
people want a similar-but-different extension, they can just copy what I
did and substitute their own module name.
Disadvantages: Requires a bit more code in Build.PL, but it would be
boilerplate and unlikely to need changes. Fewer people may notice it
and use it (a mention in Module::Build's docs might help that).
I'm leaning towards #3. I'm thinking about Module::Build::Dist (or
maybe Module::Distribute).
Comments? I'm attaching my current subclass. (The process_xs &
dist_version methods would stay in My_Build.pm and be distributed with
Win32-IPC. The rest would go into Module::Build::Dist.)
On a side note, is there a good way to name methods so they won't
conflict with future versions of Module::Build? In this example, I've
prefixed them with My_.
--
Chris Madsen [EMAIL PROTECTED]
-------------------- http://www.cjmweb.net --------------------
#---------------------------------------------------------------------
package My_Build;
#
# Copyright 2007 Christopher J. Madsen
#
# Author: Christopher J. Madsen <[EMAIL PROTECTED]>
# Created: 18 Feb 2007
# $Id: My_Build.pm 233 2008-02-21 01:02:43Z cmadsn $
#
# This program is free software; you can redistribute it and/or modify
# it under the same terms as Perl itself.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the
# GNU General Public License or the Artistic License for more details.
#
# Customize Module::Build for Win32::IPC
#---------------------------------------------------------------------
use strict;
use File::Spec ();
use base 'Module::Build';
#=====================================================================
# Package Global Variables:
our $VERSION = '0.01';
#=====================================================================
sub ACTION_distdir
{
my $self = shift @_;
$self->SUPER::ACTION_distdir(@_);
$self->My_process_README;
my $release_date = $self->My_check_Changes;
$self->My_update_pod_versions($release_date);
} # end ACTION_distdir
#---------------------------------------------------------------------
# Process README, inserting version number & removing comments:
sub My_process_README
{
my ($self) = @_;
my $out = File::Spec->catfile($self->dist_dir, 'README');
print "Updating $out\n";
my $saveStats = My_Build::SaveStats->new($out); # Auto-restores
unlink $out or die;
open(IN, '<', 'README') or die;
open(OUT, '>', $out) or die;
while (<IN>) {
next if /^\$\$/; # $$ indicates comment
s/\$\%v\%\$/ $self->dist_version /ge;
print OUT $_;
} # end while IN
close IN;
close OUT;
} # end My_process_README
#---------------------------------------------------------------------
# Make sure that we've listed this release in Changes:
#
# Returns: The release date from that line
sub My_check_Changes
{
my ($self) = @_;
my $file = File::Spec->catfile($self->dist_dir, 'Changes');
my $version = $self->dist_version;
# Read the Changes file and find the line for dist_version:
open(CHANGES, '<', $file) or die "Can't open $file: $!";
my $release_date;
while (<CHANGES>) {
if (/^\Q$version\E\s*(.+)/o) {
$release_date = $1;
last;
} # end if found our dist_version in Changes
} # end while more lines in Changes
close CHANGES;
# Report the results:
die "Can't find version $version in $file" unless $release_date;
print "Version $version released $release_date\n";
return $release_date;
} # end My_check_Changes
#---------------------------------------------------------------------
# Update the VERSION section in each module:
sub My_update_pod_versions
{
my ($self, $release_date) = @_;
# Check for and load prerequisites:
eval 'Module::Build->VERSION(0.28); require Tie::File;';
if ($@) {
print STDERR ("WARNING: Need Module::Build 0.28 and Tie::File ".
"to update VERSION sections\n");
return;
} # end if prerequisites not available
# Get a list of the shipped modules:
my $pmRef = $self->rscan_dir(File::Spec->catdir($self->dist_dir, 'lib'),
qr/\.pm$/);
my $release = $release_date;
if (@$pmRef > 1) {
$release .= sprintf(' as part of %s version %s',
$self->dist_name, $self->dist_version);
} # end if this distribution contains multiple modules
# And update each one:
foreach my $module (@$pmRef) {
$self->My_update_pod_version($module, $release);
}
} # end My_update_pod_versions
#---------------------------------------------------------------------
# Update the VERSION section in a single module:
sub My_update_pod_version
{
my ($self, $pmFile, $release) = @_;
# Record the old state of the module file:
my $saveStats = My_Build::SaveStats->new($pmFile);
chmod 0600, $pmFile; # Make it writeable
my $pm_info = Module::Build::ModuleInfo->new_from_file($pmFile)
or die "Can't open $pmFile to determine version";
my $version = $pm_info->version
or die "Can't find version in $pmFile";
# Open the module file, tying it to an array:
tie my @lines, 'Tie::File', $pmFile or die "Can't open $pmFile: $!";
my $i = 0;
# Find the VERSION section:
while (defined $lines[$i] and not $lines[$i] =~ /^=head1 VERSION/) {
++$i;
}
# Skip blank lines:
1 while defined $lines[++$i] and not $lines[$i] =~ /\S/;
# Verify the section:
if (not defined $lines[$i]) {
print STDERR "WARNING: $pmFile has no VERSION section\n";
} elsif (not $lines[$i] =~ /^This (?:section|document)/) {
die "$pmFile: Unexpected line $lines[$i]";
} else {
print "Updating $pmFile: VERSION $version\n";
$lines[$i] = sprintf('This document describes version %s of %s, released
%s.',
$version, $pm_info->name, $release);
}
untie @lines;
} # end My_update_pod_version
#---------------------------------------------------------------------
# Compile an XS file, but use the version number from the module
# instead of the distribution's version number
sub process_xs
{
my $self = shift @_;
my $pm_file = $_[0];
# Get the version number from the corresponding .pm file:
$pm_file =~ s/\.xs$/.pm/i or die "$pm_file: Not an .xs file";
my $pm_info = Module::Build::ModuleInfo->new_from_file($pm_file)
or die "Can't find file $pm_file to determine version";
# Tell dist_version to use it:
local $self->{My_Build__pm_version} = $pm_info->version
or die "Can't find version in $pm_file";
# Now that dist_version is lying, process the XS file:
$self->SUPER::process_xs(@_);
} # end process_xs
#---------------------------------------------------------------------
# Lie about the version number when necessary:
sub dist_version
{
my $self = shift @_;
return $self->{My_Build__pm_version}
if defined $self->{My_Build__pm_version};
$self->SUPER::dist_version(@_);
} # end dist_version
#=====================================================================
# Save and restore timestamp and access permissions:
package My_Build::SaveStats;
sub new
{
my ($class, $path) = @_;
my @stat = stat($path) or die "Can't stat $path: $!";
bless {
path => $path,
stat => [EMAIL PROTECTED],
}, $class;
} # end new
#---------------------------------------------------------------------
# Automatically restore timestamp & permissions:
sub DESTROY
{
my ($self) = @_;
my $path = $self->{path};
my $stat = $self->{stat};
utime @$stat[8,9], $path; # Restore modification times
chmod $stat->[2], $path; # Restore access permissions
} # end DESTROY
#=====================================================================
# Package Return Value:
1;