brian has been a Perl user since 1994. He is founder of the first Perl Users Group, NY.pm, and Perl Mongers, the Perl advocacy organization. He has been teaching Perl through Stonehenge Consulting for the past five years, and has been a featured speaker at The Perl Conference, Perl University, YAPC, COMDEX, and Builder.com. Contact brian at [email protected]
Last year (TPJ, March 2003), I showed how to create a Perl distribution for a script, but I did not give any way to actually create those distributions other than doing a lot of typing by hand. Since then, while tidying up my own scripts, I created a program I call "scriptdist" to automate all of the things I was doing manually. I also want to make it as easy as possible for other people to create distributions so they can pass around scripts that are easy to test and easy to install. Once I automate the repetitive, boring parts, I have more time to concentrate on the rest of the script lifecycle.
My program is only one of a few scripts that try to do this. The h2xs program, designed to turn C header files into the appropriate Perl glue, can make module and script distributions. It has several command-line options to turn various things on or off, but other than that, I am stuck with the files it creates and what it puts in those files. On the other hand, the mmm (My Module Maker) program by Mark Fowler can create distribution files from external templates. The ExtUtils::ModuleMaker module by R. Geoffrey Avery acts as a replacement for h2xs and comes with a modulemaker script to interactively create a distribution.
I decided to create something new after several years of dealing with h2xs, which creates a structure that I mostly discard and stub files that I do not use. I have to interact with the modulemaker script and that is too much work. The mmm script is nice, but it does not go as far as I want it to go, and I have to tell it to include things, which is too much work, too. I want things to happen by default, and the less I have to type on the command line, the better it works out. Everyone seems to have a difference in preference for this process, so no option is going to please everyone, but with enough options, most people should find one that works for them.
I have been working on and adding to this script every time I use it, and Soren Anderson has been a faithful tester and patcher who got a lot of the features to actually work on more than just my computer. By the time you read this, we may have done even more. The scriptdist distribution is on CPAN (http://search .cpan.org/) and SourceForge (http://sourceforge.net/projects/ brian-d-foy/). I even used this script to build its own distribution.
My program automates what I have usually done to create a distribution, all of which is repetitive workthe realm of computers and scripts, not humans. Given a script file by itself, I build a distribution around it. So far, scriptdist expects the script file to actually exist, but I want to fix that so it will create the script from a template as well.
Suppose I want to build a distribution for my fictitious Perl script named "mimi." I simply give the script name to scriptdist as its sole argument. The script figures the rest out on its own (but then, I programmed it to think like me).
% scriptdist mimi
As I write this, that is all there is to it. scriptdist then does its thing:
brian$ scriptdist mimi Processing mimi... Making directory mimi.d... Making directory mimi.d/t... Checking for file [.cvsignore]... Adding file [.cvsignore]... Checking for file [.releaserc]... Adding file [.releaserc]... Checking for file [Changes]... Adding file [Changes]... Checking for file [MANIFEST.SKIP]... Adding file [MANIFEST.SKIP]... Checking for file [Makefile.PL]... Adding file [Makefile.PL]... Checking for file [t/compile.t]... Adding file [t/compile.t]... Checking for file [t/pod.t]... Adding file [t/pod.t]... Checking for file [t/prereq.t]... Adding file [t/prereq.t]... Checking for file [t/test_manifest]... Adding file [t/test_manifest]... Adding [mimi]... Opening input [mimi] for output [mimi.d/mimi] Copied [mimi] with 0 replacements Creating MANIFEST... Remember to commit this directory to your source control system. In fact, why not do that right now? Remember, 'cvs import' works from within a directory, not above it.
The script first creates a directory to hold all of the files. It uses the script name with an appended ".d," but stops if that directory already exists so it will not overwrite what I may have already done. The particular directory name turns out to be only temporary, though, as I will show later. It also creates the t directory for test scripts.
Once scriptdist creates the directory structure, it checks for template files in the ~/.scriptdistrc directory in my home directory. If it finds a file, it copies it to the new distribution directory. It does not matter what the file name isthe entire directory structure of ~/.scriptdistrc, other than CVS and subversion files, ends up in the distribution directory, preserving the structure. This way, I can set up the distribution in my favorite way, regardless of what the script wants to do. For each template file, scriptdist replaces the strings SCRIPTDIST_SCRIPT with the name of the script and SCRIPTDIST_VERSION with 0.10. Eventually, I want to add more such replacements and to make their values configurable, but these are good enough for me so far.
After scriptdist copies the template files, it goes through an internal list of files that I want to put in the distribution, although it skips files that already exist from the previous step. This way, I only have to create new templates if I do not like the default versions. Inside scriptdist, I hard coded templates for each of these files just the way that I like them (but probably not the way anyone else does, hence the templating mechanism). Alternately, if someone likes scriptdist but not the internal templates, he or she is free to change them so that it is still a single file.
I automatically add a bunch of test files, including: compile.t, which ensures the script compiles so I can bail out of the rest of the tests if the script needs fixing; a test for the pod that forces me to write the documentation alongside the code; and a test to ensure that the prerequisite modules for the script show up in Makefile.PL. These last two tests only run if I have installed Test::Pod (created by me originally, but greatly improved and now developed by Andy Lester) or Test::Prereq (also by me).
Next, scriptdist copies the script into the directory. Originally, I created scriptdist to create distributions for scripts that I already had. I would like scriptdist to automatically create the script (perhaps with a template). The technological fix for this is easy, but I have to work on the script template a bit, and only that and laziness has kept this feature out of scriptdist.
Finally, scriptdist creates the MANIFEST file by calling ExtUtils::Manifest::mkmanifest. The ExtUtils::* modules have several functions I can use to work with distributions, and browsing through a Makefile shows a lot of them in action.
At the end of the run, scriptdist reminds me to import the new directory into my source control system, and since I often forget which directory I am in when I do this, scriptdist reminds me to change into the script directory before I import the distribution into CVS. When I import the directory into CVS, I choose the name for the (CVS, not Perl) module. The current directory name makes no difference because I need to check out the (again, CVS, not Perl) module to continue to work on my script distribution. Later, I would like to automate these steps as well.
I still have a short list of things I need to automate, although I am waiting until I get out of the Army (and by the time you read this, that should only be a couple of weeks) to make any more changes. I would like scriptdist to automatically create the PREREQ_PM value for WriteMakefile() in Makefile.PL, and I am planning to do that along with some changes to Test::Prereq. I would like to add command-line switches to control the behavior of scriptdist, so I can choose between features such as using the usual Makefile.PL or Module::Build's Build.PL method. I would also like to add support for Module::Release, the tool created out of my "release" program to automate the other side of the distribution lifecycle.
This program is a work in progress, but it does most of my script distribution set-up work, and it works for a few other people as well. I have a lot of things I would like to add to it. Most of the features do things the way that I like, but I want to make that more flexible. I appreciate any comments and suggestions TPJ readers may have.