Makefile.PL best practices

Matts on 2001-12-18T15:57:43

I've been writing a *lot* of perl modules lately (mostly internal stuff), and so I've been thinking a lot about what are the best practices for writing modules... Here's my random thoughts:

1. Don't use h2xs. It just sucks so bad. Here's my recipe for creating a new perl module:

mkdir <ModuleName> cd <ModuleName> mkdir t mkdir lib mkdir lib/<ModuleName> (make any subdirs too) touch README MANIFEST MANIFEST.SKIP Makefile.PL Changes

touch lib/<ModuleName>.pm touch t/00basic.t

Note that sometimes there you'll need to make more directories under lib/ if your module is deeply nested.

2. Edit Makefile.PL to look something like:

use ExtUtils::MakeMaker; WriteMakefile( 'NAME' => '<ModuleName>', 'VERSION_FROM' => 'lib/<ModuleName>.pm', 'PREREQ_PM' => {}, 'ABSTRACT_FROM' => 'lib/<ModuleName>.pm', 'AUTHOR' => 'Matt Sergeant <matt@sergeant.org>', );

Note that I don't put any of the *useless* comments in that h2xs gives you... I mean, really do we need VERSION_FROM to have a comment that says "finds $VERSION" ???

3. Edit t/00basic.t to look something like:

use Test; BEGIN { plan tests => 1 } END { ok($loaded) } use <ModuleName>; $loaded++;

4. Edit MANIFEST.SKIP to look something like:

CVS/.* \.bak$ \.sw[a-z]$ \.tar$ \.tgz$ \.tar\.gz$ ^mess/ ^tmp/ ^blib/ ^Makefile$ ^Makefile\.[a-z]+$ ^pm_to_blib$ ~$

5. Start editing lib/<ModuleName>.pm

I'm not going to tell you how to write a module here. But once again, a lot of the h2xs cruft I used to manually remove by hand, but now I just write my own module, making sure I use strict, end in "1;", and add some POD.

Now run perl Makefile.PL, then make manifest, then make realclean.

A couple of the "best practices" that the above does, that I consider important are:

1. You get the lib/ structure for your module, rather than the default h2xs method of dumping .pm files in the main directory of your project. This allows you to do fast direct testing using perl -Mlib=lib -M -e 'test code'

2. You start off right away with tests in the t/ directory, and they use the Test.pm module. Note some often un-mentioned features of Test::Harness - the ability to specify TEST_VERBOSE=1 and TEST_FILES=t/01foo.t on the command line, which gives you more verbose output, and the ability to do individual tests without having to run your entire test suite:

make test TEST_VERBOSE=1 TEST_FILES=t/01foo.t

This will also tell you exactly where the test failure was (a line number) and why it failed, if you're using Test.pm's ok() function (e.g. "got 'flugebar', expected 'dungbeetle'").

The only thing I really miss is the ability to have .xs files deep in the hierarchy. I think this is a MakeMaker bug in not allowing that.

Anyway, hope this helps someone.


OK. Thanks!

jdavidb on 2001-12-20T15:16:56

#!/usr/local/bin/perl5.6.1

use warnings;
use strict;

my($MODNAME) = @ARGV;

mkdir($MODNAME);
chdir($MODNAME);
mkdir("t");
mkdir("lib");
mkdir("lib/$MODNAME");
my $fh;
foreach my $file (qw(README MANIFEST MANIFEST.SKIP Makefile.PL Changes))
{
    open($fh, ">$file");
}
open($fh, ">lib/$MODNAME.pm");
open($fh, ">t/00basic.t");

open($fh, ">Makefile.PL") || die "Can't open Makefile.PL: $!";
print $fh <<"EOF";
use ExtUtils::MakeMaker;
WriteMakefile(
        'NAME' => '$MODNAME',
        'VERSION_FROM' => 'lib/$MODNAME.pm',
        'PREREQ_PM' => {},
        'ABSTRACT_FROM' => 'lib/$MODNAME.pm',
        'AUTHOR' => 'Matt Sergeant <matt\@sergeant.org>',
);
EOF
close($fh) || warn "Couldn't close Makefile.PL: $!";

open($fh, ">t/00basic.t") || die "Can't open t/00basic.t: $!";
print $fh <<"EOF";
use Test;
BEGIN { plan tests => 1 }
END { ok(\$loaded) }
use $MODNAME;
\$loaded++;
EOF
close($fh) || warn "Couldn't close t/00basic.t: $!";

open($fh, ">MANIFEST.SKIP") || die "Can't open MANIFEST.SKIP: $!";
print $fh <<'EOF';
CVS/.*
\.bak$
\.sw[a-z]$
\.tar$
\.tgz$
\.tar\.gz$
^mess/
^tmp/
^blib/
^Makefile$
^Makefile\.[a-z]+$
^pm_to_blib$
~$
EOF
close($fh) || warn "Couldn't close MANIFEST.SKIP: $!";

# Just to be cute ;)
exec("$ENV{EDITOR} lib/$MODNAME.pm");

Re:OK. Thanks!

jdavidb on 2001-12-20T15:19:35

Matt, can't tell you how helpful you've been to me in the past. Hope this helps you a little, or at least spurs some further thought. :) Obviously I'll be immediately updating my own personal copy of this not to say matt@sergeant.org for AUTHOR.

Being the freak that I am, I'll also probably add strict and warnings to the test script, and add the package $MODNAME; and 1; statements into $MODNAME.pm.

Re:OK. Thanks!

Matts on 2001-12-20T16:39:42

One thing this misses is the schizophrenic relationship in here between module name and module path - something I washed over in my journal entry.

Re:OK. Thanks!

jdavidb on 2001-12-20T17:52:10

Yes. I started thinking about that and then decided I would never get the program written, let alone posted, if I handled that issue.

But, as the rough rocks in a stream get worn down smooth, I'm sure I will walk across that enough times in the future I (or someone else) will be motivated to fix that.

Hmmmmm. How about Inline::Files to hold to contents of all those files that get set up?

Re:OK. Thanks!

jdavidb on 2001-12-21T16:17:24

Done. :) It's in my journal, but I'm not sure if anyone's seen it. Am I the only one who ever looks at journals at random? I've got the journals slashbox at the top on my page.

Re:OK. Thanks!

koschei on 2001-12-28T07:16:20

Don't know about other people, but my RSS fetcher regularly grabs the list of most recently changed journals and incorporates them into a news feed. It's somewhat indiscriminate in that it includes everyone, not just 'friends'. But that's good =)

It would be nice to see a feature that lists journal entries since a particular date.