Adventures in building Perl modules (a short, short primer on extending Module::Build)

Over the last few years, it has been a presumption that when I work on a project in Perl, I will use the standard Perl tools– @ExtUtils::MakeMaker@ and, later, @Module::Build@ –for managing the Perl library code I write.

But yesterday, for the first time, I looked at extending @Module::Build@ to do more than just the stock actions. And you know what, it was easy.

Now the specific issue I was running up against was that I needed to insure that the database I was running my tests against was installed and clean. I had been using a Makefile, but that was a hack–for instance, I wasn’t actually checking the presence of the database or anything, I was looking for a file I wrote when I created the database. I probably could have made make check for the actual database, but it’s imperative, rather than procedural, style makes this kind of ugly.

Also, I wanted a clean instance of this database before I did a test run of the conversion utility (this is all work on a heavily revised “AnteSpam”:http://antespam.com/, and we’re moving from keeping config info in ldap to putting it in a replicated PostgreSQL database). _And_ I wanted a clean instance of this database before I ran the “PostgreSQL Autodoc”:http://www.rbt.ca/autodoc/ tool to generate a nice diagram and DocBook documentation of the structure.

Oh, and I got so frigging tired of SQL’s spectacular verbosity ( *badly* exacerbated by the fact that I was commenting on most of the structures so the information would show up in the DocBook documentation) that I wrote a simple preprocessor–so I had to make sure that was run if necessary before creating the database.

Oh, and I wanted to build the documentation automatically. And I kept forgetting to run the @Build@ script with the environment set properly for the database, so I wanted that to be handled easily.

So, I made a file, @Build.pm@, which sits right alongside @Build.PL@, and subclasses Module::Build. And to that file I added a function (admittedly *very* simplistic, and, as a result, somewhat overenthusiastic) to drop and recreate the database:

bc.. sub create_db {
my $self = shift;

# Get the database name
my $database = $self->args (“database”);

# Drop the database if it already exists
$self->do_system (qq{dropdb $database}) if ($self->do_system (qq{psql -l | egrep -q $database}));

# Create the database
$self->do_system (qq{createdb $database});

# Make sure the schema is up-to-date
$self->dispatch (“ddlpp”);

# Load up the schema and initial data
$self->do_system (qq{psql -q -f antespam.sql});
};

p. There are several cool things here. First, you can look at, at run-time, arguments that were given to the script when it was created. So you can do:

@perl Build.PL database=foo@

and when you actually invoke the resulting build script, the bits you write can look for a database argument, and use what was set initially. The rest of it should be fairly obvious–yes, I’m just shelling out to psql rather than doing it all in DBI–except for the dispatch call. You see, you can add additional actions to your script. In this case, I added an action called @ddlpp@ (for DDL pre-processor) to build the sql from my data definition file. It’s short, just:

bc.. sub ACTION_ddlpp {
my $self = shift;
$self->do_system (qq{ddlpp antespam.dp antespam.sql}) unless ($self->up_to_date (“antespam.dp”, “antespam.sql”));
};

p. You’ll notice, though, that it will only run @ddlpp@ again if the @.dp@ file is newer than the @.sql@ file. That’s cool.

Anyway, I also overrode the standard @test@ action, to make sure the database is created:

bc.. sub ACTION_test {
my $self = shift;

# Set up database access
local $ENV{PGDATABASE} = $self->args ("database");
local $ENV{PGHOST} = $self->args ("host");
local $ENV{PGPASSWORD} = $self->args ("password");
local $ENV{PGUSER} = $self->args ("user");

# Make sure the database is created
$self->create_db;

# Run tests as normal
$self->SUPER::ACTION_test (@_);
}

p. All this does is set the appropriate environment variables for psql to pick up, creates the database, and then runs the normal test action that it inherited from @Module::Build@. The @convert@ action is similar, except it shells out to the convert script.

Etc., etc. I’m not holding this up as any paragon of implementation–in fact, it’s exposed some shortcuts I’ve taken that I ought not be taking, so I’m gonna have to clean those up eventually, and I should be able to add automatic @.dp@ to @.sql@ conversion and so forth–but for an hour or two of poking around, I’ve made some not-inconsequential extensions to the build system, giving me a much cleaner, more integrated process.

Published by

Michael Alan Dorman

Yogi, brigand, programmer, thief, musician, Republican, cook. I leave it to you figure out which ones are accurate.