James E Keenan
Toronto Perlmongers
Thursday, October 27, 2005
Who here hasn't used modulemaker?
[Get volunteer from audience and have him/her sit at keyboard.]
Please come up here.
To create a Perl extension that is ready for uploading to CPAN, all you have to do is type this:
$ modulemaker
... and then answer the questions!
[Prompt audience for a module name, abstract, author info, compact, etc. Have volunteer enter info and then generate the distribution.]
Okay, now type this:
$ perl Makefile.PL; make; make test; make dist
Now c<cd> into the directory just created.
This distribution is ready to be uploaded to CPAN!
What you've just seen was the brainchild of Philadelphia-area Perl hacker Geoff Avery. Geoff created ExtUtils::ModuleMaker and its command-line utility, modulemaker four years ago. Since July of this year I have been the maintainer of ModuleMaker. Tonight I'd like to show you what I have done with ModuleMaker and -- more importantly -- what I've learned in the process.
I heard Geoff speak about ModuleMaker at YAPC::NA::2003 in Boca Raton and then again a month later in Paris at YAPC::EU::2003. He later told me that he very consciously developed a plan to speak about it at a number of conferences so that he could get his employer to spring for the airfare to those conferences. But I didn't immediately begin to use it.
A year ago this week, however, things changed. I was planning a trip to New Orleans for early December to visit a musician friend who had moved there from New York six years earlier. As you Torontonians well know, I'm the sort of guy who times his visits to other cities based on when their local Perlmonger chapters meet. The NewOrleanians met on the second Friday of the month, so that's when I flew south. Before I did so, I joined their mailing list and checked out their wiki. I noticed they had a page on the wiki called ''Call for Speakers'' (http://neworleans.pm.org/index.cgi) and I figured: Why not?
It turned out that I was the very first out-of-town guest speaker they had ever had. Nary a Damian, nor a Randal nor an Abigail had graced their presence. To my surprise, I qualified as a visiting expert. After some discussion, we decided that I should speak on building a CPAN-ready Perl distribution. I talked about the usual stuff. You know ... writing your documentation first, then writing your tests, finally writing your code. (You guys know all about that, right?)
Then I faced the question: How should I tell the neworleans.pm folks to create the structure for a CPAN-ready distribution? Did I really want them to say:
$ h2xs -Axn Module::Name
I myself had used h2xs exactly once. Why should I use such an obscurely-named utility and have to type three options just to get that utility's most typical use?
Then I remembered ModuleMaker. I decided to check it out. Once I tried out the interactive mode, I was amazed at how well it reflected one of the three great Perl virtues. I wrote Geoff Avery, ''modulemaker is truly fucking Lazy!''
Now, I immediately added, ''I do have some criticisms, but to those in a
moment.'' And, as you might guess, those criticisms were the itch that I had
to scratch. And scratch. And scratch. I noticed that the Makefile.PL
created by using ExtUtils::ModuleMaker version 0.32 had a key for ABSTRACT,
but the modulemaker script failed to prompt the user for an ABSTRACT.
A patch here, a patch there, and pretty soon you're talking about real money.
By the time of my New Orleans talk I knew I wanted to do a lot of work on
ModuleMaker.
But about that time Geoff became preoccupied with mundane matters such as getting married and he stopped answering his e-mail. I didn't hear from him for so long that I had to resort to asking other Philadelphia-area Perlmongers if Geoff was okay. But by January I had become involved in Perl Seminar NY's Phalanx project work on Text-Template and HTML-Template. I set ModuleMaker aside to work on a project in which, as you know, we had to deal with the problem of unresponsive module authors on a much broader scale.
Fast forward to YAPC in Toronto in June. Geoff and I get to talking, and he speaks the words I had secretly been waiting to hear: ''Jim, why don't you take over ModuleMaker's maintenance?'' Deal done. I thought I'd be able to do what I wanted to do in about a month's time. Little did I know that I would be spending every evening and weekend for the rest of the summer working on improvements in ExtUtils::ModuleMaker.
If you heard the talk that Marc Prewitt and I gave on Phalanx at YAPC (http://hew.ca/yapc/phalanx/slides/index.html), you'll recall that I said that I now use Subversion for version control on all my projects. So the first thing I did was to check Geoff's last version of ModuleMaker, 0.32, into my repository. I then pulled in all the revisions I had done the previous November, so I could get an immediate svn diff on those changes. I used Subversion religiously; the evidence is here in the printout of my svn log for this project.
[ Toss out svn log to audience. ]
At this point I knew that before I added new functionality to ExtUtils::ModuleMaker, I would have to clean up the internals without damaging the currently existing functionality. How did I do that? By writing documentation, of course.
Or, more precisely, by editing the existing documentation to make sure that it constitutes an accurate specification of the current, non-deprecated functionality.
While the user of a computer program might use undocumented functionality, he should never rely on it. When a programmer takes over maintenance of existing code, it's his job to make sure that all functionality is documented and that the documentation covers only functional code. The maintainer must either document previously undocumented code or explicitly deprecate it.
Upon inspection I found that some of ExtUtils::ModuleMaker's then current functionality was not documented, or only sketchily so. The modulemaker utility was the best thing about ExtUtils::ModuleMaker, but its documentation was lacking. I immediately set out to improve it. I also discovered a number of subroutines in lib/ExtUtils/ModuleMaker.pm that were not documented (or, for that matter, tested) and which appeared to constitute code left over from earlier in ExtUtils::ModuleMaker's development. That code got scrapped. There were other subroutines that Geoff had documented but explicitly deprecated. I had no intention of maintaining deprecated functionality, so I scrapped both the code and the documentation.
The result: The documentation found in v0.33 -- the first uploaded to CPAN under my maintenance -- reflected the current functionality much more closely.
What's true of documentation is also true of testing: first, do no harm. If the maintainer finds code that is untested, he either has to write tests for it, explicitly warn the user of untested features, or scrap it.
So the next thing
I did was to write more tests and increase the test suite's coverage of
ExtUtils::ModuleMaker's code. Those of you who heard Marc and me speak at
YAPC will recall how we measured our progress during Phalanx by using CPAN
module Devel::Cover to measure our code coverage. Running make test and
Devel::Cover on v0.32 gave these results:
v0.32: test files: 4 tests: 37
Coverage:
File stmt bran cond sub
---------------------------- ------ ------ ------ ------
...b/ExtUtils/ModuleMaker.pm 80.7 48.4 44.4 82.5
Running Devel::Cover on v0.33 yielded this:
v0.33: test files: 7 tests: 105
Coverage:
File stmt bran cond sub
---------------------------- ------ ------ ------ ------
...b/ExtUtils/ModuleMaker.pm 95.1 64.1 47.6 97.1
You can see that I improved the coverage substantially. To jump ahead a minute to v0.43 (the current CPAN version):
v0.43: test files: 91 tests: 2054
Coverage:
File stmt bran cond sub
---------------------------- ------ ------ ------ ------
...b/ExtUtils/ModuleMaker.pm 96.9 82.8 100.0 100.0
.../ModuleMaker/Auxiliary.pm 87.8 42.3 33.3 100.0
...s/ModuleMaker/Defaults.pm 100.0 n/a n/a 100.0
...duleMaker/Initializers.pm 100.0 87.5 77.8 100.0
...duleMaker/StandardText.pm 94.7 76.1 100.0 100.0
...ls/ModuleMaker/Utility.pm 78.8 50.0 n/a 100.0
We'll see that adding new functionality required splitting the code up into six packages instead of the original one. However, most of the improvements in code coverage were made between versions 0.32 and 0.33. That's because between these two versions I was writing tests for previously untested code rather than writing tests for new functionality.
Having corrected the bugs I spotted a year ago, improved the documentation and written tests to improve the coverage, I uploaded v0.33 to CPAN on July 8.
From this point forward my work on ExtUtils::ModuleMaker reflected five impulses:
Let's look at each in turn.
When you take over maintenance of code originally written by someone else, you never really feel comfortable with it until you've imposed your own version of ''best practices'' on it. This imposition doesn't change the code one iota (at least, it shouldn't), but it makes it easier for you to manipulate. So over the course of several versions I found myself:
All these changes mean that if you were to diff Geoff's last version against my first version, you would get an enormous diff output. But when I look back and factor out the effect of these cosmetic changes, there wasn't much about Geoff's code that was simply wrong. And in certain cases I adopted Geoff's style of doing things in situations where I would have started out differently. For example, in documenting subroutines Geoff used a tabular style rather than the paragraph style I previously would have used:
Usage : $self->default_values()
Purpose : Defaults for new().
Returns : A hash of defaults as the basis for new().
Argument : n/a
Throws : n/a
Comments :
If, however, I were asked to point out one area where I improved already
existing functionality, I would point to the fact that I sharpened the focus
of the two most important publicly available methods new() and
complete_build(). Broadly speaking, the constructor loads values into the
ExtUtils::ModuleMaker object while complete_build() uses those values to
create the necessary directories and files and to give those files the desired
content. In Geoff's version 0.32, a method called verify_values() was
called toward the beginning of complete_build() to rule out obviously wrong
values such as e-mail addresses without an @ sign. My own opinion is that
validation of attributes should be done as one stage within the constructor.
So my most recent version has that method -- now renamed validate_values()
-- inside new() rather than in complete_build().
When I took over maintenance of ExtUtils::ModuleMaker in July, I did not immediately have plans to add new functionality. In mid-July, however, I met a Perl hacker who argued that someone who was using modulemaker repeatedly would not want to constantly re-type his name, e-mail address or web site. He would want to save this information -- along with any preferences as to the content of files created -- in a personal defaults file stored on disk.
In July I also became aware that in 2004 -- while Geoff Avery's attention had shifted away from ExtUtils::ModuleMaker and I was just learning about it -- Andy Lester had uploaded a distribution called Module::Starter which, like ExtUtils::ModuleMaker, creates the directories and files needed for a CPAN-ready Perl extension. Module::Starter also has a command-line utility, module-starter, analogous to modulemaker -- though module-starter lacks the interactive mode which makes modulemaker so incredibly easy to use.
And, as befits the new public relations director of the Perl Foundation, Andy did a good job at promoting Module::Starter. It's specifically mentioned in both Damian's new book and in the new Perl Testing: A Developer's Handbook by Ian Langworth and chromatic (http://www.oreilly.com/catalog/perltestingadn/). Damian, in fact, created a Perl extension called Module::Starter::PBP which creates the framework for a CPAN-ready Perl distribution which follows the practices he recommends in his book. Module::Starter prompts the user upon first use to store a limited amount of personal information in a defaults file.
I figured that if Module::Starter could, um, duplicate ExtUtils::ModuleMaker's overall functionality, I could, um, borrow one of Module::Starter's original concepts and include it in an improved ExtUtils::ModuleMaker.
To implement this, I needed a
publicly available method, make_selections_defaults(), that could be called
within a Perl program once an ExtUtils::ModuleMaker object had been
constructed. In addition, I needed to offer a command-line option for
modulemaker that would trigger make_selections_defaults(), and I needed
to modify the modulemaker interactive mode for that same purpose.
I manage to accomplish all that -- but testing it, as we shall see in a moment -- proved to be hairy.
Let me just note that, while make_selections_defaults() was the principal
new functionality added, I also added a number of attributes which permit more
fine-grained control over the content of the lib/*.pm files and the names
and content of the t/*.t files. These will mainly be of interest to users
interested in subclassing ExtUtils::ModuleMaker.
To demonstrate what could be done with these attributes, I subclassed ExtUtils::ModuleMaker to create another distribution now up on CPAN, ExtUtils::ModuleMaker::PBP, which uses ExtUtils::ModuleMaker to create a framework in the Damian-approved style identical to Module::Starter::PBP. (Damian gave his blessing to my borrowing his boilerplate copy for my implementation of PBP.)
Setting aside two packages which held code for licenses and one package intended to be an example of subclassing, v0.32 of ExtUtils::ModuleMaker consisted only of lib/ExtUtils/ModuleMaker.pm and the modulemaker utility. Over two-and-a-half months of development, I moved much of the code in those files to other packages, largely for the purpose of making subclassing easier.
The default values were the first to go, moving to
lib/ExtUtils/ModuleMaker/Defaults.pm. A hash holds the default values; a
method returns a reference to that hash. If you subclass
ExtUtils::ModuleMaker and want to change some of the default values, you will
create a default_values() method in the subclass, call
$self->SUPER::default_values() from the superclass, then replace
the values for individual attributes as needed.
The other methods called within ExtUtils::ModuleMaker::new() were next,
going to lib/ExtUtils/ModuleMaker/Initializers.pm. Since these methods
mainly create composite attibutes or perform simple validation on values
provided to the constructor, I don't anticipate much need to subclass them.
Most subclassing activity will be focused on the quasi-public methods called
within ExtUtils::ModuleMaker::complete_build(). These methods have been
moved to lib/ExtUtils/ModuleMaker/StandardText.pm. These methods create
directories and files and write content to those files. There are currently
26 such methods. A fellow Brooklyn Perl hacker and module author was
instrumental in encouraging me to provide these methods with a
common look and feel.
Most of the code for the modulemaker utility was refactored out of scripts/modulemaker and into lib/ExtUtils/ModuleMaker/Interactive.pm. The options handling code was then transferred to lib/ExtUtils/ModuleMaker/Opts.pm. These two refactorings mean that if you subclass ExtUtils::ModuleMaker, you can very easily create a command-line utility for your subclass that offers the same functionality that the modulemaker utility offers to the parent class. Here, for example, is all the relevant content of the mmkrpbp utility I wrote for ExtUtils::ModuleMaker::PBP:
use ExtUtils::ModuleMaker::PBP::Interactive;
use ExtUtils::ModuleMaker::Opts;
my $opt = ExtUtils::ModuleMaker::Opts->new(
q{ExtUtils::ModuleMaker::PBP},
q{mmkrpbp},
);
my $mod = ExtUtils::ModuleMaker::PBP::Interactive->new(
$opt->get_standard_options()
);
$mod->run_interactive() if $mod->{INTERACTIVE};
$mod->complete_build();
Only six statements, by my count!
Most of the time I spent on ExtUtils::ModuleMaker between July and September of this year was spent not on improving the code but on overhauling the test suite. As noted above, I went from 4 test files holding 37 tests to 91 test files holding 2054 tests. Why the big increase?
Largely because, as a veteran of the Phalanx project, I've become much more rigorous about testing. In particular, if I'm testing code that creates files and directories and if I'm running those tests on someone else's machine, I don't want those files to be left around when the testing is done. I want them to be created in temporary directories which are automatically removed once testing is complete.
For that purpose, I use the File::Temp module which has shipped with the Perl
core since 5.6. It works quite well, though not as well on Windows as on
Unix-like systems. My use of File::Temp, however, was somewhat restricted by
the fact that I wanted to keep the working code in ExtUtils::ModuleMaker as
pre-5.6-compatible as possible. I don't, for example, use our or use
warnings; in the module. If I made File::Temp an absolute prerequisite for
running the test suite, the suite would fail horribly on older Perls -- and
there are many good Perl programmers who are required to work in pre-5.6
environments.
One alternative would be to add File::Temp to the PM_PREREQ key in
ExtUtils::ModuleMaker's own Makefile.PL, thereby forcing all users to
install File::Temp. But I'm averse to requiring users to install CPAN modules
purely for testing purposes, and I have no idea how the latest version of
File::Temp would work on Perl 5.4 or 5.5.
Another alternative would be to include a copy of File::Temp underneath the
t/ directory -- an alternative I did take with some parts of the CPAN
distribution called IO::Capture. But IO::Capture is pure Perl. File::Temp
sits atop prerequisites which in turn sit on top of C-code brought in with XS.
For me, that ruled out including File::Temp in the ExtUtils::ModuleMaker
distribution.
The path I took, as I'm sure you've guessed, was to enclose all the tests that
required File::Temp inside SKIP blocks and make locating Perl 5.6 or later
the condition for entering those blocks. This means that programmers using
older Perls will get the benefit of ExtUtils::ModuleMaker's improved
functionality, but they'll have to take the accuracy of that functionality
largely on faith, as most tests are skipped on older Perls.
As mentioned earlier, the single most significant improvement I made to
ExtUtils::ModuleMaker's functionality was to permit the user to save a file on
disk with his/her personal selections for attribute values. Whether the user
initiates these selections in a script, from the modulemaker command-line
or from modulemaker's interactive mode, the personal defaults file is
ultimately saved via the make_selections_defaults() method. Since this
method, when used in production, saves a file on the user's disk outside of
the normal Perl library directories and usually in the user's home directory,
to test the method I had to write tests that asked:
$ENV{HOME} is
undefined?
make test? Can I restore that personal defaults file afterwards?
Ultimately, I felt I demonstrated that I could do all that. But I had to test each step along the way to ensure that the testing process itself was sound and did not leave any footprints on the user's disk after testing was complete. This ballooned the size of each test file, but I was able to refactor a lot of this testing code out into subroutines and ultimately into two packages included with the distribution, lib/ExtUtils/ModuleMaker/Auxiliary.pm and lib/ExtUtils/ModuleMaker/Utility.pm.
The interactions between using File::Temp, using SKIP blocks and testing to
make sure that I could safely test the personal defaults file were quite
daunting. Until the most recent versions, I had 13 or 14 files in the test
suite, though some of those files had several hundred tests each subdivided
into individual SKIP blocks. But these interactions gave some of the CPAN
testers hiccups, which I could only occur by breaking these massive test files
up into smaller files with only one SKIP block and one call to File::Temp
per file. This multiplied the number of test files enormously, but I took a
suggestion from Langworth and chromatic and created several subdirectories
under t/, each of which corresponded to an individual t/*.t file in an
earlier version of ExtUtils::ModuleMaker.
Over the course of two-and-a-half months I received considerable assistance in the form of feedback from beta testers and testers on the perl.cpan.testers list (a.k.a. http://testers.cpan.org).
Some of this feedback consisted of reports of errors that were very easily corrected. At one point, for instance, I failed to include the IO::Capture packages I had placed underneath t/testlib/ in the MANIFEST. So they didn't get wrapped up in the tarball and didn't get uploaded to CPAN. Tests that depended on them failed. I revised the MANIFEST and uploaded a new version. Later, I learned how to use make manifest to bring my MANIFEST up to date, and how to use a MANIFEST_SKIP file to make sure that no unwanted files are added to the MANIFEST and then to the tarball.
Two test files test whether a user who prefers Module::Build over
ExtUtils::MakeMaker can create the Build.PL filed needed with
modulemaker. At first I failed to account for the possibility that a
different user would not have Module::Build installed at all. So the tests
using Module::Build, like those mentioned earlier using File::Temp, had to go
in a SKIP block.
One day in July, purely on a whim, I used Randy Kobes' search engine to look up ExtUtils::ModuleMaker on CPAN (http://cpan.uwinnipeg.ca/search). Much to my surprise, I found that someone whom I had never heard of had written a module which subclassed Geoff Avery's version of ExtUtils::ModuleMaker. Suddenly I had to take into account the possibility that the revisions I was going to make would break someone else's code.
That someone was David Golden, and that subclass was ExtUtils::ModuleMaker::TT, a module to create Template Toolkit-style templates. Naturally I sought the counsel of my fellow Perl hackers as to how to proceed. Fortunately, there was an ny.pm meeting scheduled at the Peculier Pub for that very evening. I was the first to arrive. As I was sitting there nursing a Dogfish 90, in walks David Golden! Neither I nor anyone else in ny.pm had ever met him -- notwithstanding the fact that he had attended YAPC in Toronto along with us!
David's collaboration was really helpful, though the results were surprising. Later that month I went over to his house one evening -- and those of you who know New York City know how rare going over to a fellow geek's house is -- met his lovely wife -- I won't comment on how rare that is or not -- and heard what he had to say about ExtUtils::ModuleMaker and how it should evolve. David pointed out that he had implemented the concept of a personal defaults file in ExtUtils::ModuleMaker::TT well before Module::Starter did so, and he strongly advocated that I do so for ExtUtils::ModuleMaker as well. He also urged me to rationalize the naming of variables and subroutines, to clean up the internals somewhat along the lines of Module::Starter to make subclassing easier, and to promote ExtUtils::ModuleMaker on the basis of the way in which it offers users more sophisticated ways to use it as they become more familiar with it.
But remember that I said a while ago that when I first discovered someone had subclassed ExtUtils::ModuleMaker, I had to worry if my revisions would break that someone else's code? Well, my revisions broke David Golden's subclass, ExtUtils::ModuleMaker::TT. That module was one of David's first to go up on CPAN, and he conceded that its code was very tightly tied to that of Geoff Avery's version of ExtUtils::ModuleMaker. But its purpose -- creation of Template::Toolkit-style templates -- is not as close to that of ExtUtils::ModuleMaker as it first appeared. Of course, it doesn't help that I know next to nothing about Template::Toolkit. By mid-September, I had to decide what features of ExtUtils::ModuleMaker I would be prepared to support for the indefinite future. I came to the conclusion that I wouldn't be able to guarantee support for all the features David wanted. He's busy patching over the differences even as we speak.
As challenging as David Golden 's feedback was, it was less shocking than
the feedback which came from the people who do automated testing of CPAN
distributions. As you probably know, these testers send automated test
reports which grade your distribution on a PASS/FAIL basis. If you FAIL,
you get a printout equivalent to what you yourself would get if you called
make; make test. You then have to figure out why your test suite --
which presumably PASSed with flying colors when you ran it on your own
box -- FAILed at some specific point on someone else's box. You might
also have to figure out why it passed on one tester's Linux 2.4 box while
failing on another tester's Linux 2.4 box. And when you upload a new
version, you may get a FAIL from someone who PASSed your earlier version.
Some of the CPAN testers install everything they PASS -- and install all the prerequisites they had to install to attain that PASS. This can impart a subtle bias in favor of PASSing to tests that they run in the future on later versions of the same distribution. After all, if you've already installed all of a distribution's prerequisites, you can never thenceforward validly test what a distribution should do if its prerequisites are not found.
Other CPAN testers -- notably imacat from Taiwan -- maintain a very pristine testing environment. It was quite an effort to get ExtUtils::ModuleMaker and ExtUtils::ModuleMaker::PBP to pass on both her Linux and Cygwin boxes. The source of the problem was the testing of the placement of the personal defaults file. Before I knew how to make sure that my testing removed the testing version of the personal defaults file from her home directory, I was leaving that version there, which polluted the environment when she went to test a later version.
Some might say that maintenance programming is less interesting or less sexy than ... what would you call programming that's not maintenance programming? Non-maintenance programming? Initial design? The mathematical complement of maintenance programming?
I would argue that the difference between non-maintenance programming and maintenance programming is the difference between imagining a diamond and polishing a diamond.
I'd rather have a diamond that's polished than one that has merely been imagined.
Good maintenance programming practices are good programming practices, only moreso.
But, to move beyond generalities: On the basis of my experience with Phalanx and with ExtUtils::ModuleMaker, I would say that maintenance programming comes with a certain set of expectations:
The main task still facing me is testing of the modulemaker utility's interactive mode. Because it displays a menu, prompts the user for input, and then displays a new menu based on that input, it has to be tested with an Expect-like tool such as the Expect, Expect-Simple or Test-Expect modules on CPAN. But I have not been able to figure out how to use those modules. They are either too complex for me to grasp when I have lots of other coding problems to think about, or their documentation is too fragmentary to give me an idea as to how to proceed. If any of you have experience using these modules, please let me know.
The other task will be what I'm now doing: Letting people know that ExtUtils::ModuleMaker:
Thank you very much. Where's the pub?