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;

Reply via email to