Exploring the Distribution

Chapter 4 - CPAN Module Distributions
by?Sam Tregar?
Apress ? 2002
has companion web siteCompanion Web Site

Like most things Perl, there's more than one way to build a module distribution. The module skeleton generated by h2xs is designed to be simple and generic enough to be useful for all sorts of modules. It's a great place to start, but there are some modifications that will be helpful for you to know. I'll also explore some of the useful things that the build system can do for you without any modifications at all.

Testing

The test script generated by h2xs is simply a Perl script that uses the Test module to run tests. Adding a new test is very simple—just add a few lines to the script that call ok() and update the plan number accordingly. This works well for simple modules, but a complicated module that uses this system will end up with a very large test script. Also, some modules contain features that are difficult to test in a single script.

Fortunately, ExtUtils::MakeMaker allows you to have as many separate test scripts as you need. To use this functionality, create a directory in your module distribution called t. Then create your test files, ending with the extension .t, inside this directory. For example, a typicalt directory might contain the files 01load.t, 02basic.t, and 03errors.t. The resulting directory structure is shown in Figure 4-2. Using this layout, make test looks a little different.

 $ make test PERL_DL_NONLAZY=1 /usr/local/bin/perl -Iblib/arch -Iblib/lib \ -I/usr/local/lib/perl5/5.6.1/i686-linux -I/usr/local/lib/perl5/5.6.1 \ -e 'use Test::Harness qw(&runtests $verbose); $verbose=0; runtests @ARGV;' t/*.t t/01load......ok t/02basic.....ok t/03errors....ok All tests successful. Files=3, Tests=6, 0 wallclock secs ( 0.20 cusr + 0.04 csys = 0.24 CPU) 

click to expand
Figure 4-2: Distribution directory structure using a test directory

This output is a summary of the output for each of the test scripts. Also included is some timing information that can be useful in performance optimization.[13]

If one of the tests has failures, then the output will look like the following:

 $ make test PERL_DL_NONLAZY=1 /usr/local/bin/perl -Iblib/arch -Iblib/lib \ -I/usr/local/lib/perl5/5.6.1/i686-linux -I/usr/local/lib/perl5/5.6.1 \ -e 'use Test::Harness qw(&runtests $verbose); $verbose=0; runtests @ARGV;' t/*.t t/01load......ok t/02basic.....FAILED test 2         Failed 1/2 tests, 50.00% okay t/03errors....ok Failed Test Stat Wstat Total Fail Failed List of Failed ------------------------------------------------------------------------------- t/02basic.t                2   1  50.00% 2 Failed 1/3 test scripts, 66.67% okay. 1/6 subtests failed, 83.33% okay. 

Here test number 2 in t/02basic.t has failed. The test output is clear enough, but it doesn't include any line numbers. To get the actual output from each of the test scripts, you can add the TEST_VERBOSE=1 option to the make test run:

 $ make test TEST_VERBOSE=1 PERL_DL_NONLAZY=1 /usr/local/bin/perl -Iblib/arch -Iblib/lib \ -I/usr/local/lib/perl5/5.6.1/i686-linux -I/usr/local/lib/perl5/5.6.1 \ -e 'use Test::Harness qw(&runtests $verbose); $verbose=1; runtests @ARGV;' t/*.t t/01load......1..1 ok 1 ok t/02basic.....1..2 ok 1 not ok 2 # Failed test 2 in t/02basic.t at line 5 FAILED test 2        Failed 1/2 tests, 50.00% okay t/03errors....1..3 ok 1 ok 2 ok 3 ok Failed Test Stat Wstat Total Fail Failed List of Failed ------------------------------------------------------------------------------- t/02basic.t                2    1 50.00% 2 Failed 1/3 test scripts, 66.67% okay. 1/6 subtests failed, 83.33% okay. 

Now the filename and line number where the test failed is visible—t/02basic.t, line 5.

Note 

Numbers are added to the front of the t/*.t test script names because make test runs the test files in sorted order. By ordering your test scripts, you can test basic functionality at the start of the test run and reduce the time it takes to detect simple errors.

Another way to improve your testing is to use the Test::More module instead of Test. Test::More[14] lets you write your tests in such a way that failing tests provide much more information. For example, consider a test case that verifies that the count() function returns a count of its arguments:

 ok(Data::Counter::count('foo', 'bar') == 2); 

If this test fails, then all you're told is that the test failed.

If instead you used Test::More's is() function, then you'll get a much more useful error:

 is(Data::Counter::count('foo', 'bar'), 2); 

The is() function takes two arguments—the operation to be tested and the expected value. If count() returns 3 instead of 2, then the test will fail with the following message:

 #    Failed test (t/testname.t at line 3) #         got: '3' #    expected: '2' 

Armed with the expected results and the actual return value, you may not even need to look at the test script to start debugging. Test::More includes testing functions for applying regular expressions (like()), comparing complex data structures (eq_array(), eq_hash(), eq_set(), and is_deeply()), and examining objects (can_ok() and isa_ok()). Test::More also contains support for skipping tests when they are known to fail under certain environments and marking tests as todo items that are not expected to pass. See the Test::More documentation for details on all this and well, more!

Debugging

Perl comes with a command-line debugger similar to the popular gdb C debugger. If you've never used the Perl debugger before, you should look at the perldebug documentation to get started. To run the Perl debugger on a single test.pl test script, use the command make testdb:

 $ make testdb PERL_DL_NONLAZY=1 /usr/local/bin/perl -d -Iblib/arch -Iblib/lib \ -I/usr/local/lib/perl5/5.6.1/i686-linux -I/usr/local/lib/perl5/5.6.1 test.pl Default die handler restored. Loading DB routines from perl5db.pl version 1.07 Editor support available. Enter h or 'h h' for help, or 'man perldebug' for more help. 1..1 main::(test.pl:4):    ok(1); # If we made it this far, we're ok.   DB<1> 

However, if you're using a t directory of test scripts, then a simple make testdb won't work. This is because the debugger will only work on a single script. To use the debugger on a single .t file, include the option TEST_FILE. For example, to run t/02basic.t under the debugger, you would use the following command:

 make testdb TEST_FILE=t/02basic.t 

Multimodule Distributions

A module distribution can contain more than one module file. A common use for packaging multiple files in a distribution is to install a family of modules in a common namespace. For example, the HTML::Template::JIT[15] module distribution contains the three modules HTML::Template::JIT, HTML::Template::JIT::Compiler, and HTML::Template::JIT::Base. The main module file, JIT.pm, is placed where you would expect it—in the module distribution directory. The other two, Compiler.pm and Base.pm, are placed in a directory called JIT. Makefile.PL automatically finds the two submodules and installs them along with JIT.pm.

An alternate method of packaging multiple modules within a distribution is to create a lib directory inside your distribution. Inside lib you then create the full module path for each included module. If this were done with HTML::Template::JIT, then the path to JIT.pm inside the module distribution would be lib/HTML/Template/JIT.pm. Note that you would have to modify Makefile.PL's VERSION_FROM and ABSTRACT_FROM to point to the new location of JIT.pm. Using lib provides a more flexible system since it allows a distribution to contain modules with different root names collected under a common tree.

Executable Scripts

Module distributions can contain more than just modules, they can also contain executable scripts. For example, the LWP[16] module distribution contains a script called lwp-download that uses the LWP modules to download files from the Web. When you install the LWP distributions on your system, this script and others are installed alongside Perl's executable scripts, usually somewhere in your PATH.

Including scripts with your modules can serve two useful purposes. First, they allow nonprogrammers to access the functionality of your module. Second, they can provide examples for programmers of how to use your module to accomplish a simple task.

By default ExtUtils::MakeMaker installs any file ending in .pl other than test.pl in the top-level module directory as an executable script. See the ExtUtils::MakeMaker documentation for details and a description of the PM option to WriteMakeFile() that can be used to search for .pl files (and .pm files) in other locations.

Self-Modifying Code

If you look at the LWP distribution, you'll see that the scripts aren't distributed as .pl but as .PL files. The reason for this is that they need to be processed before they can be installed on the user's machine. To see why this is necessary, consider the script file in Listing 4-10, count_args.pl, that counts its arguments. If this file were distributed as-is with Data::Counter, it wouldn't work on many users' systems. This is because the first line contains the path to the perl executable. Since this path varies from system to system, count_args.pl would only work on other systems where perl is installed in /usr/bin.

Listing 4-10: Nonportable count_args.pl

start example
 #!/usr/bin/perl -w use Data::Counter qw(count); print count(@ARGV), "\n"; 
end example

The solution to this problem is shown in Listing 4-11. ExtUtils::MakeMaker will execute any script ending in .PL at build time and use the output as the source for the script to install. The name of the file to be generated must be the name of the script preceding the .PL. This follows the pattern set by Makefile.PL generating Makefile. In the example shown, to generate count_args.pl, I created a file named count_args.pl.PL. This script uses the Config module to generate the correct perl execution line for the start of the script file.

Listing 4-11: Portable count_args.pl.PL

start example
 use Config; # used to get at startperl # open output script and make it executable open OUT,">count_args.pl" or die "Can't create count_args.pl: $!"; chmod(0755, "count_args.pl"); # output perl startup line print OUT $Config{startperl}, " -w \n"; # output the rest of the script print OUT q{    use Data::Counter;    print Data::Counter::count(@ARGV), "\n"; }; 
end example

This mechanism can be used to do more than just extract platform information from the Config module. Some overly clever module authors use .PL scripts in zany code-generation schemes to gain flexibility and performance. I'll admit to being guilty of this charge, but I hesitate to suggest the practice to the sane!

Building the Distribution Archive

Building the distribution archive with ExtUtils::MakeMaker is as simple as running make dist (after running perl Makefile.PL, of course). This is the command that generates all those nice .tar.gz files available on CPAN. The make dist command is the payoff for all the hard work that goes into using h2xs and Makefile.PL.

Here's what the make dist output looks like on my Linux system:

 $ make dist rm -rf Data-Counter-0.01 /usr/local/bin/perl -I/usr/local/lib/perl5/5.6.1/i686-linux \ -I/usr/local/lib/perl5/5.6.1 -MExtUtils::Manifest=manicopy,maniread \ -e "manicopy(maniread(),'Data-Counter-0.01', 'best');" mkdir Data-Counter-0.01 mkdir Data-Counter-0.01/t tar cvf Data-Counter-0.01.tar Data-Counter-0.01 Data-Counter-0.01/ Data-Counter-0.01/t/ Data-Counter-0.01/t/03errors.t Data-Counter-0.01/t/01load.t Data-Counter-0.01/t/02basic.t Data-Counter-0.01/README Data-Counter-0.01/MANIFEST Data-Counter-0.01/count_args.pl.PL Data-Counter-0.01/Changes Data-Counter-0.01/Makefile.PL Data-Counter-0.01/Counter.pm rm -rf Data-Counter-0.01 gzip --best Data-Counter-0.01.tar 

A .tar.gz distribution file is created called Data-Counter-0.01.tar.gz. The contents of the file are taken from your MANIFEST file. (Here's where all your hard work keeping it up-to-date finally pays off!) Conveniently, the format for the distribution filename is exactly what CPAN expects.

ExtUtils::MakeMaker also provides a convenient way to make sure your new distribution will pass a make test after being unpacked in an empty directory—make disttest. Running make disttest will catch missing files in your distribution, although be aware that it won't catch a missing test file from your test directory since make test doesn't know what it's missing. To explicitly check your MANIFEST file, use the command make distcheck. The output will list files in your distribution that aren't in your MANIFEST file.

Do It Yourself

Sometimes the way ExtUtils::MakeMaker does things isn't the way you want to do them. One common example is that your module can optionally use some other modules (as opposed to prerequisite modules that are required for your module to work) to provide advanced functionality. It wouldn't be appropriate to place such modules in PREREQ_PM since that will cause them to be installed by people who don't intend to use the advanced features. However, you might not want to be totally silent about the choice either.

An example of this situation can be found in the Net::FTPServer[17] module. If the user installs the Archive::Zip[18] module, then Net::FTPServer will enable FTP clients to request zipped versions of files. To alert users during installation to this option, Net::FTPServer includes code inside Makefile.PL that checks for Archive::Zip and prints a message if it's not found:

 Checking for optional module Archive::Zip >= 0.11 ... not found. *** Archive::Zip is missing. This module is required if you want to enable archive mode, which allows users to create ZIP files on the fly from directories on the server. 

The code in Net::FTPServer's Makefile.PL then calls sleep(1) to give the user the chance to read this message before the usual flurry of make output continues. Some modules handle this situation by prompting the user, asking whether to continue without the optional module or abort the installation process. This ensures that users will read the message, but at the cost of breaking unattended installation. If you've ever had the experience of firing up a lengthy CPAN.pm installation, getting a cup of coffee, and coming back to find that the first module stopped the installation to ask a question, then you'll probably avoid this path!

Adding custom code in Makefile.PL works fine for checking module dependencies, but if you need to modify the generated Makefile, then you'll need to extend ExtUtils::MakeMaker itself. ExtUtils::MakeMaker provides a simple way to do this: Just define subroutines in the MY package, and they'll be called automatically instead of the normal ExtUtils::MakeMaker methods.[19]

I ran into a situation like this while developing the Guile[20] module. As you may have noticed earlier, the default make test implementation sets the PERL_DL_NONLAZY environment variable when it runs the test scripts. For reasons best left unexplored, this setting makes testing the Guile module impossible; the underlying libguile libraries I was using don't pass the PERL_DL_NONLAZY tests. What I needed to do was to filter out the PERL_DL_NONLAZY setting but otherwise leave the make test code in the Makefile as-is. To accomplish this, I added a subroutine called MY::test to the end of my Makefile.PL:

 package MY; sub test {   my $pkg = shift;   my $test = $pkg->SUPER::test(@_);  # call the real test() from MY parent   $test =~ s/PERL_DL_NONLAZY=1//g;   # filter out PERL_DL_NONLAZY setting   return $test;                      # return the modified test block } 

This subroutine filters the output of the real test() subroutine and removes the troublesome PERL_DL_NONLAZY setting. To get a list of subroutines that you can extend in MY, see the documentation for the ExtUtils::MM_Unix module.

start sidebar
Alternatives to h2xs

In Perl, there is nearly always more than one way to do any given task, and building a module distribution is no exception. It's a good bet that most of the module distributions on CPAN were built with h2xs, but that hasn't stopped people from trying to build better tools.

ExtUtils::ModuleMaker is intended to bring generating module templates "into the 21st century", according to its author, R. Geoffrey Avery. The module offers the ability to operate from the command line similarly to h2xs or to be called from a script file to access more advanced functionality. One unique feature of ExtUtils::ModuleMaker is its ability to generate license text and a full LICENSE file based on a selection of open-source licenses. For more details, see the module documentation, available on CPAN.

Sean M. Burke's makepmdist script offers an entirely different take on building a module distribution from h2xs and ExtUtils::ModuleMaker. Instead of generating templates for you to edit and maintain, it offers a fast path to building a module distribution from a single module file. You simply give it your module file as an argument, and out pops a .tar.gz file ready to upload to CPAN. The module file must be the only module in the distribution, and in order to specify prerequisites and tests it must adhere to a POD convention specified by makepmdist. However, for very simple modules it offers a time-to-upload that just can't be beat! You can find the script in Sean's author directory on CPAN—authors/id/S/SB/SBURKE/.

However, even with these working alternatives some modules require the services of h2xs. For example, XS modules (introduced in Chapter 9) are only supported by h2xs. I suggest you learn to useh2xs and then explore these alternatives once you're familiar enough to draw your own conclusions.

end sidebar

[13]See Devel::DProf for a better way.

[14]Written by Michael Schwern and available on CPAN

[15]HTML::Template::JIT provides a just-in-time compiler for HTML::Template. The module was written by myself, Sam Tregar, and is available on CPAN.

[16]LWP provides client and server modules for all manner of network communication. LWP was written by Martijn Koster and Gisle Aas and is available on CPAN.

[17]Available on CPAN, this module implements an FTP server (almost) entirely in Perl. It was written by Richard Jones, Rob Brown, Keith Turner, Azazel, and many others.

[18]Written by Ned Konz and available on CPAN

[19]This works because the MY package inherits from the MM package that ExtUtils::MakeMaker uses to abstract platform dependencies. See the ExtUtils::MakeMaker documentation for the details.

[20]This module provides a Perl binding to the GNU Guile Scheme interpreter. It is available on CPAN.



Writing Perl Modules for CPAN
Writing Perl Modules for CPAN
ISBN: 159059018X
EAN: 2147483647
Year: 2002
Pages: 110
Authors: Sam Tregar

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net