This is an advance chapter of Mastering Perl by brian d foy. It is incomplete and may contain merely notes or an outline for future work. It may also contain sections of brian's writing from other sources, although these are noted where possible.
This work is copyrighted under a contract between O'Reilly Media and brian d foy, and you cannot repost it or distribute it without permission.
Although there are over 10,000 distributions on CPAN, sometimes it doesn't have exactly what I need. Sometimes a module has a bug, or needs a new feature.
I can do several things, and no solution is the right answer for every situation. I like to go with the solutions that mean the least amount of work for me and the most benefit for the Perl community, although those aren't always compatible. For the rest of this section, I won't give you a straight answer. All I can do is point out some of the issues involved so you can figure out what's best for you.
The least amount of work, in most cases, is to fix anything you need and send the diffs to the author so that he can incorporate them in the next release of the module. There's even a bug tracker for every CPAN module on http://rt.cpan.org, and the module author automatically gets an email notifying him about the issue.
Sometimes the author is available, has time to work on the module, and releases a new distribution. In that case, I'm done. On the other hand, CPAN is mostly the result of a lot of volunteer work, so the author may not have enough free time to commit to something that won't pay his rent or put food in his mouth. Even the most conscientious module maintainer gets busy sometimes.
To be fair, even the seemingly simplest fixes aren't trivial matters to all module maintainers. Patches hardly ever come with corresponding updates to the tests or documentation, and the patches might have consequences to other parts of the modules or to portability. Furthermore, patch submitters tend to change the interface in ways that work for them, but somehow make the rest of the interface inconsistent. Things that seem like five minutes to the submitter might seem like a couple of hours to the maintainer, so make it onto the To-Do list rather than the Done list.
If I can't get the attention of the module maintainer, I might just make changes to the sources myself. Doing it this way usually seems like it works for a while, but when I update modules from CPAN my changes might disappear as a new version of the module overwrites my changes. I can partially solve that by making the module version very high:
our $VERSION = 99999;
This has the disadvantage of making my job tough if I want to install an official version of the distribution that the
maintainer has fixed. That version will most likely have a smaller number, so tools such as CPAN.pm and CPANPLUS will think my patched version is up-to-date and
won't install the seemingly older version over it.
Other people who want to use my software might have the same problems, but they won't realize what's going on when things break after they update seemingly unrelated modules. Some software vendors get around this by creating a module directory about which only their application knows and putting all the approved versions of modules, including their patched versions in that directory. That's more work that I want, personally.
If the module is important to you (or your business) and the author has disappeared, you might consider officially taking over its maintenance. Although every module on CPAN has an owner, the administrators of the Perl Authors Upload Server (PAUSE)[1] can make you a co-maintainer or even transfer complete ownership of the module to you.
The process is simple, although not automated. First, send a message to modules@perl.org
inquiring about the module status. Often, the administrators can reach the author when you cannot because the author recognizes
their name. Second, the admins will tell you to publicly announce your intent to take over the module, which really means to
announce it publicly where most of the community will see it. Next, just wait. This sort of thing doesn't happen quickly because
the administrators give the author plenty of time to respond. They don't want to transfer a module while an author's on holiday,
for instance!
Once you take over the module, though, you've taken over the module. You'll probably find that the grass isn't greener on the other side and at least empathize with the plight of the maintainers of free software, starting the cycle once again.
The last resort is forking, or creating a parallel distribution next to the official one, is a danger of any popular open source projects. It's been only on very rare occasions that this has happened with a Perl module. PAUSE will allow you to upload a module with a name registered to another author, and the module will show up on CPAN, but PAUSE will not index it. Since it's not in the index, the tools that work with CPAN won't see it even though CPAN stores it.
You don't have to use the same module name though. If you choose a different name, you can upload your fixed module, PAUSE will index it under its new name, and the CPAN tools can install it automatically. Nobody knows about your module though because everyone uses the original version with the name they already know about and the interface they already use. It might help if your new interface is compatible with the original module, or at least provides some sort of compatibility layer.
I might just decide to not use a third-party module at all. If I write the module myself, I can always find the maintainer. Of course, now that I'm the creator and the maintainer, I'll probably be the person about whom everyone else complains. Doing it myself means i have to do it myself.
The best solution, if possible, is a subclass that inherits from the module I need to alter. My changes live in its own source files and I don't have to touch the source of the original module. We mostly covered this in our barnyard example in Intermediate Perl, so I won't go over it again here[2]
Before I do anything, I create an empty subclass. I'm not going to do a lot of work if I can't even get it working when I haven't changed anything yet. For this example, I want to subclass the Foo module so I can add a new feature. I can use the Local namespace, which will never conflict with a real module name.
package Local::Foo
use base qw(Foo);
1;
If I'm going to be able to subclass this module, I should be able to simply change the class name I use and everything should still work.
#!/usr/bin/perl
# use Foo
use Local::Foo;
#my $object = Foo->new();
my $object = Local::Foo->new( ... );
The next part depends on what I want to do. Am I going to completely replace the a feature or method, or do I just want to add a little bit to it? I add a method to my subclass. In most cases, I probably want to call the super method first to let the original method do its work
package Local::Foo
use base qw(Foo);
sub new
{
my( $class, @args ) = @_;
... munge arguments here
my $self = $class->SUPER::new( @_ );
... do my new stuff here.
}
1;
ExtUtils::MakeMaker works for most module
installers, but if it doesn't do something that I need, I can easily change it through subclassing. In this case, ExtUtils::MakeMaker uses the special subclass
name My. Before it calls its hard-coded methods, it looks for
the same functions in My, and will use those preferentially.
As MakeMaker performs its magic, it writes to the file Makefile according to what its method tell it to do. What it decides to write comes from ExtUtils::MM_Any, the base class for the magic, and then perhaps a subclass, such as ExtUtils::MM_Unix or ExtUtils::MM_Win32, that might override methods for platform specific issues.
In my Test::Manifest module, I want the make test step to execute the test files in the order I specify rather than the order in which glob returns the filenames from the t directory. The function test_via_harness writes out a section of the Makefile[3]:
sub test_via_harness {
my($self, $perl, $tests) = @_;
return qq{\t$perl "-MExtUtils::Command::MM" }.
qq{"-e" "test_harness(\$(TEST_VERBOSE), '\$(INST_LIB)', '\$(INST_ARCHLIB)')" $tests\n};
}
After interpolations and replacements, the output in the Makefile shows up as something like this, although results differ by platform.
test_dynamic :: pure_all
PERL_DL_NONLAZY=1 $(FULLPERLRUN) "-MExtUtils::Command::MM" "-e" "test_harness($(TEST_VERBOSE), '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES)
After boiling everything done, a make test essentially runs a command that globs all of the
files in the t directory, and runs them in that order[4].
perl -MExtUtils::Command::MM -e 'test_harness( ... )' t/*.t
It doesn't matter much what test_harness actually does as long as my replacement does the same
thing. In this case, I don't want the test files to come from @ARGV because I want to control
their order.
If I want to change how that works, I need to get my function in the place of test_harness. By
defining my own subroutine and assigning it to *MY::test_via_harness. I can put any text I like in
place of what the normal test_via_harness. In this case, I want to use my function from Test::Manifest.
package Test::Manifest;
*MY::test_via_harness = sub
{
my($self, $perl, $tests) = @_;
return qq|\t$perl "-MTest::Manifest" | .
qq|"-e" "run_t_manifest(\$(TEST_VERBOSE), '\$(INST_LIB)', | .
qq|'\$(INST_ARCHLIB)', \$(TEST_LEVEL) )"\n|;
};
In my run_t_manifest subroutine, instead of taking the list of files as arguments, I look in
the file t/test_manifest, where I list the test files to run, optionally commenting lines I want to skip. I
list them in any order I like and that's the order I'll run them:
load.t
pod.t
pod_coverage.t
#prereq.t
new.t
feature.t
other_feature.t
By subclassing the module, I don't have to fool with MakeMaker, which is certainly something I don't want to do. I get the feature I want, and I don't break things for anyone else. I still have the same ExtUtils::MakeMaker source that everyone else has. If I need to change any other behavior in MakeMaker, I go through the same process.
XXX: See the stuff in the Perl debugger chapter
For another example of subclassing, see the chapter on Pod, where I subclass Pod::Simple. In that case, the author wrote the module specifically for others to subclass.
If I can't subclass the module for some reason[5], I can take advantage (or, as some people would say, "abuse") Perl's extremely permissable and malleable package and class system. I can just replace the parts that I need. This is a bit dangerous.
There are many ways that I can do this. In Chapter RRR and RRR, I replaced subroutines by messing with typeglobs.
XXX: Hook::LexWrap