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, 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 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:
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}); };
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:
sub ACTION_ddlpp { my $self = shift; $self->do_system (qq{ddlpp antespam.dp antespam.sql}) unless ($self->up_to_date ("antespam.dp", "antespam.sql")); };
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:
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 (@_); }
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.