 |  | Contents: |  | |
 | Related content: |  | |
 | Subscriptions: |  | |
| The module that makes Makefiles and much more
Sean Dague (sldague@us.ibm.com) Software Engineer, IBM Linux Technology Center 1 November 2001 If
you've used UNIX or Linux for some period of time, you've probably
written a few Perl programs to automate simple tasks. Each of these
programs does something basic and simple that might otherwise take you
10 or 20 minutes to do by hand. In this article, Sean will show you how
to convert just such a Perl program into a far more robust programming
project, one that will be generic enough to be widely distributed
across many disparate platforms. Programming products vs. programs
In Frederick Brooks' The Mythical Man-Month he asks the question:
Why are there all these stories about a couple of people in a garage
creating a new program in significantly less time than it takes a
larger entity or corporation to produce the same program, with the
same amount of function? To describe this conundrum he distinguishes between two types of software: the program, and the
programming product. A program is a piece of software that is functional within one
environment. This could be the set of scripts lying around in your
local bin directory. A programming product requires taking a program
and adding a number of additional items. This includes thorough and
usable documentation, an easy installation procedure, and a set of
test cases to ensure the program does what it is supposed to do.
Brooks theorizes that creating a programming product requires at least
thee times more effort than creating a program. Although this may seem like
a great deal of work, this effort is rarely in vain.
The program now becomes useful to
significantly more people, and even more useful to yourself. How many
times have you found that you need to read the source code of your
scripts every time you try to use them? Wouldn't it be easier to consult a man page for each of your scripts? Perl is often thought of as a Swiss Army Knife or a large roll of
duct tape. No one who works with UNIX for any length of time can
avoid eventually writing some Perl code. Unfortunately, many people
don't realize that Perl also contains a robust infrastructure for
building programming products in the Brooksian sense.
The heart of this infrastructure is a Perl module called ExtUtils::MakeMaker .
At its core, MakeMaker provides a Perl interface to generating
Makefiles. However, it does much more than this. The Makefiles
created by MakeMaker will not only build and install the project, but
will also generate documentation in man page or HTML formats, and run an
automated set of test cases against the entire application. Because this function is so tightly bound to the core Perl infrastructure, the
overhead in learning and using it is small. The advantages, however, are vast.
Once proper test cases have been created for your application,
your project will never regress without your knowledge. Any change
that breaks old function will abruptly throw up red flags. This gives
you a much greater assurance that you are not reintroducing old bugs
with each new patch you add to your code. In contrast to Brooks' factor of three work increase, I have found that by using these methodologies I actually save
considerable time. Once a bug is fixed it is probably fixed. Documentation
that is embedded alongside the code that it describes is easier to
keep up to date than documentation that exists externally.
Plus, you get an installation script for free. This article will not teach you how to write Perl, but it will show you how to
turn a Perl program into a far more robust programming project.
That project will be generic enough to be widely distributed
across many disparate platforms. Anatomy of a MakeMaker project
On the cover of Programming Perl, 3rd Edition is the phrase "There's More
Than One Way To Do It". This is the mantra of the Perl community. The flexibility
of the language allows for many vastly different but functionally
equivalent solutions to any given problem. Building Perl projects with MakeMaker is no different than any other aspect of
Perl: there are many ways to do it. For simplicity I will present only
one approach. I consider this to be a set of best practices, based on my
experience with many of the respected projects that exist on CPAN, the Comprehensive Perl Archive Network (see Resources later in this article).
As you become accustomed to building projects with MakeMaker , feel free to
develop your own style for constructing them. A basic Perl project will consist of a number of files and directories contained
within your project's directory. I'll touch on each one
briefly, then revisit each in turn as I construct a Perl application. Makefile.PL
The heart of a Perl project is the Makefile.PL file. Makefile.PL is a Perl script
that generates a gnu Makefile for your entire project. This is done through
the invocation of the WriteMakefile function that is contained within
the ExtUtils::MakeMaker library. A simple Makefile.PL looks something like: Minimal Makefile.PL
use ExtUtils::MakeMaker;
WriteMakefile(
'NAME' => 'myproject',
'VERSION_FROM' => 'lib/MyModule.pm', # finds $VERSION
);
|
In this example I have passed WriteMakefile the only two pieces of information
that are required: NAME and VERSION . Name is specified explicitly. Version is
specified implicitly through the use of the VERSION_FROM variable. This
indicates to MakeMaker which module contains the
authoritative $VERSION variable for the project. All other variables required to construct the Makefile will be fetched from the Perl
interpreter defaults. These include things like PREFIX , for the prefix in which
the applications should be installed (generally defaulting to /usr or
/usr/local), MAN1PATH , for the location where section 1
(user command) man pages should be installed, and INSTALLSITELIB , the
location where libraries should be installed. For a complete list of variables, refer to the ExtUtils::MakeMaker man page, or the HTML
documentation located at Perl.com (see Resources). MANIFEST
The second most important file in a Perl project is the MANIFEST. The
MANIFEST is a list of all files that are a part of the project. When Makefile.PL is run,
it first examines MANIFEST, and checks that all the files required for the
project are present. If they are not, it will generate a list of missing files, and
will not build the master Makefile. MANIFEST is also used beyond mere consistency checking. One of the additional
targets provided by MakeMaker is dist , which I will discuss later. make
dist uses MANIFEST to generate its tar file. It is crucial to keep
this file up to date. One should note that MakeMaker provides a target for generating the MANIFEST called manifest .
By typing make manifest all files in the current directory and all subdirectories
are added to the MANIFEST. The manifest target does not exclude
any files by default. You can change this by creating a
MANIFEST.SKIP file. For more information, please see the
ExtUtils::Manifest man page. lib and bin directories
How you organize the files of your project is often a matter of
religion. UNIX has a tradition of having sibling directories, lib and
bin,
at many levels throughout the directory structure. I have found that if
you use this same convention in Perl projects, it is far easier to keep
track of
the role of each file. This is especially true with large
multi-developer projects. The lib directory in UNIX refers to libraries potentially used by multiple
applications. In the case of Perl, this means Perl Module (.pm) files, each
of which generally start with a package declaration, and Perl Library (.pl)
files (which generally don't have a package declaration, but consist solely
of subroutine declarations). MakeMaker looks for Perl Modules in the
project top level directory, and in the lib directory by default,
though this is configurable. The bin directory in UNIX is where binaries and other runnable
programs are stored. In the bin directory you should place all scripts
that begin with some variation of #!/usr/bin/perl . Do not be
concerned whether or not your #! line will be appropriate on another
platform. As you will see later, MakeMaker will take care of this
problem during installation. Many people like appending a .pl extension to all Perl programs for
clarity. This is not required for those programs to run. MakeMaker will work equally well with or without the suffix.
For this reason I tend not to suffix my Perl applications with any special
endings. t/, the test case directory
One of the most important parts of the MakeMaker framework is the ability to have
automated test cases. This function is provided by the Test::Harness module,
which specifies a well-defined interface for creating test scripts containing
one or more test cases. During the test phase of building a Perl Project, MakeMaker will run all
files in the t subdirectory that have the .t ending as test scripts. It
will also run test.pl in the project root directory as a test script, so be
careful not to accidentally create such a program unless you actually intend
for it to conform to the Test::Harness interface. The test phase is an integral step in the Perl build process. It is so integral
that often an application will not install unless all the tests succeed. Writing
robust test scripts will not only make it much easier for you to guarantee the
quality of your project, but will also allow others to more easily port your code
to a new environment. Instead of merely stating that it does work on X hardware,
they can inform you that test cases 15-18 in testabc.t failed. This gives you
far more information about what actually went wrong, and will lead to a
faster and more reliable fix. I will describe in depth how to write test scripts and test cases later in this
article. Plain Old Documentation
One of the biggest issues for most projects is keeping documentation in sync with
the code that actually implements it. When code is updated to include a new feature
or change the way an existing one behaves, it can become difficult and time
consuming to track down and modify all the documentation pertaining to that
feature. This becomes especially true if documentation needs to be kept in
multiple formats, for instance man pages and HTML. One of the very important features that Perl provides is an embedded documentation
markup language, POD (Plain Old Documentation). POD is extremely simple, and
consists of very few markup
commands. POD markup text is considered a comment by the Perl interpreter, so
you may have POD documentation distributed throughout your code. The core Perl
distribution comes with parsers that convert POD to text, HTML,
and man page format. There are contributed modules on CPAN for
direct translation to Latex, PDF, and Postscript as well. During the Perl build process, MakeMaker will check all scripts and modules for
POD documentation. On a UNIX platform, it will generate section 1 for
all executables, and section 3 man pages for each library. You may
optionally also generate HTML documentation
during installation. If you have properly documented your program in POD, after
a user installs your program, then he or she can run man your-program and get full documentation
on it. This is better than expecting the user to chase down a
readme file somewhere, and it creates a familiar interface for your documentation. More information on POD can be found in Programming Perl, 3rd Edition, by running
man perlpod , or online at Perl.com (see Resources). Your first MakeMaker project (make)
This sample MakeMaker project is inspired by postings on the perl5-porters
mailing list by Colin McMillen concerning the Net::Ping modules.
Ping is a very useful tool for determining whether or not a machine is accepting
packets, but often you want to know more than that, like whether a Web server
is actually responding on that machine. Also, in an era of ICMP attacks, many hosts
have actually disabled returning standard ping packets, so it no longer is a useful indication
of network connectivity. This project will be to build a program
pingwww, which will look and act a lot like ping, but will actually be sending
HTTP requests, and testing whether or not the Web server in question is functioning. This project will contain a core module, Net::Ping::HTTP , which will implement
the guts of what is happening, and pingwww, a program that provides
a user interface to Net::Ping::HTTP . Writing the Makefile.PL
I will begin by writing the Makefile.PL. At first it has only the most basic
entries in it. Makefile.PL for Net::Ping::HTTP, take 1
use ExtUtils::MakeMaker;
WriteMakefile(
'NAME' => 'Net::Ping::HTTP',
'VERSION_FROM' => 'lib/Net/Ping/HTTP.pm', # finds $VERSION
);
|
This specifies that the name of the project is Net::Ping::HTTP , and that
the $VERSION variable contained within lib/Net/Ping/HTTP.pm will be
considered the authoritative version of the project. Now I have a Makefile.PL, and one other file in the project.
It is now time to create the MANIFEST, so that the build process will work
correctly. The first pass at a MANIFEST will look as follows: MANIFEST, take 1
MANIFEST
Makefile.PL
lib/Net/Ping/HTTP.pm
|
Remember, every time new files are added to the project, an entry for each
of them must also be added to the MANIFEST file. Net::Ping::HTTP
The code for this module will be
a simple layer on top of LWP::UserAgent , which is included in the libwww-perl
package. Many flavors of Linux and UNIX include this library in their distribution by default.
Also, if you
have installed ActiveState Perl for Windows, you already have LWP::UserAgent .
However, if you are unfortunate enough to be on an operating system that doesn't
provide this module by default, you may still download it from CPAN (see Resources). The strategy for Net::Ping::HTTP is simple. The HTTP protocol supports a method
HEAD , which is designed to return only HTTP headers, but no content. Net::Ping::HTTP
will send an HTTP HEAD request for the root document of the Web server you
are attempting to ping, and will return the status code that is returned by
that Web server. In the case where the Web server is available, this status
code should be 200 (though in rare cases it might be a 30x or 40x code as well). Here is a first pass at Net::Ping::HTTP , including the POD documentation for the
module: Net::Ping::HTTP library
package Net::Ping::HTTP;
=head1 NAME
Net::Ping::HTTP - An interface for determining whether an HTTP
server is listening
=head1 SYNOPSIS
my $pinger = new Net::Ping::HTTP;
my $rc = $pinger->ping('http://localhost');
my $pinger = new Net::Ping::HTTP(
TIMEOUT => 15,
PROXY => 'http://someproxy.net',
);
my $rc = $pinger->ping('http://www.ibm.com');
=head1 DESCRIPTION
Net::Ping::HTTP is a simple interface on top of LWP::UserAgent
that lets you ping a Web server to see if it is responding to HTTP
requests. This would make it suitable for monitoring Web applications.
=head1 AUTHOR
Sean Dague <sldague@us.ibm.com>
=head1 SEE ALSO
L<perl>, <LWP::UserAgent>
=cut
use strict;
use LWP::UserAgent;
use HTTP::Request;
use vars qw($VERSION);
$VERSION = '0.02';
sub new {
my $class = shift;
my %this = (
PROXY => undef,
TIMEOUT => 10,
@_,
);
my $ua = new LWP::UserAgent("Net::Ping::HTTP - $VERSION");
$this{UA} = $ua;
$this{UA}->timeout($this{TIMEOUT});
if($this{PROXY}) {
$this{UA}->proxy('http',$this{PROXY});
}
return bless \%this, $class;
}
sub ping {
my ($this, $url) = @_;
my $request = new HTTP::Request('HEAD',"$url");
my $response = $this->{UA}->request($request);
return $response->code();
}
1;
|
Net::Ping::HTTP uses both LWP::UserAgent , and HTTP::Request , and will not
work without them. Instead of having a user install this module, then have it
break because the dependencies aren't there, the
module can specify which other modules it requires to run. These modules can be added as requirements to the Makefile.PL. Their existence
will be verified before the master Makefile is generated.
The format of this new variable for Makefile.PL is as follows: Makefile.PL prerequisites
'PREREQ_PM' => {
Module::Name => minimum.version.number,
...
},
|
The code works with the version of LWP::UserAgent and HTTP::Request that I
have installed on my workstation, so I will prereq those versions in my module. To determine
those versions, I can run the following commands on the command line: Finding module versions
perl -MLWP::UserAgent -e 'print $LWP::UserAgent::VERSION'
perl -MHTTP::Request -e 'print $HTTP::Request::VERSION'
|
The -M flag from perl loads that Module, the -e flag runs the following
string as the perl script. With the information collected, the Makefile.PL looks as follows: Makefile.PL take 2
use ExtUtils::MakeMaker;
WriteMakefile(
'NAME' => 'Net::Ping::HTTP',
'VERSION_FROM' => 'lib/Net/Ping/HTTP.pm', # finds $VERSION
'PREREQ_PM' => {
LWP::UserAgent => 1.73,
HTTP::Request => 1.27,
});
|
pingwww
The core library for the project is not complete. For it to be useful, there must
exist a program that exploits its functionality and provides a user interface.
This program
will be called pingwww and will be placed in the bin/ directory of the
project tree. Two features of the ping command that would be nice to simulate in pingwww are
the ability to specify the number of times ping sends packets out to the server before
it exits, and a synchronous display of how long each request took to respond. To add these features, the program will need two additional modules
that were not required for Net::Ping::HTTP . These modules are Getopt::Std ,
which parses command line options, and Time::HiRes , which provides
microsecond resolution timing. Getopt::Std is part of the
core Perl distribution, so there is no need to add it as a PREREQ_PM , however
Time::HiRes is not (though it appears that it will be part of the standard library
as of Perl 5.8). Time::HiRes is hence added to the PREREQ_PM variable in
Makefile.PL. Here is the first pass at pingwww: pingwww program
#!/usr/bin/perl
#
# This program is licensed under the same terms as Perl itself
#
# Sean Dague <sldague@us.ibm.com>
=head1 NAME
pingwww - program for "pinging" webservers
=head1 SYNOPSIS
pingwww [-c iterations] [-p proxy] [-t timeout] [-h] hostname
=head1 DESCRIPTION
Pingwww uses the Net::Ping::HTTP module to send HEAD requests
to the host specified by hostname.
=head1 AUTHOR
Sean Dague <sldague@us.ibm.com>
=head1 SEE ALSO
L<Net::Ping::HTTP>, L<Time::HiRes>, L<perl>
=cut
use strict;
use Carp;
use Getopt::Std;
use Net::Ping::HTTP;
use Time::HiRes qw(gettimeofday tv_interval);
$| = 1;
my %opts;
getopt('cpth',\%opts) or croak("Couldn't parse options");
if($opts{h}) {
usage();
}
my $max = $opts{c} || 1000;
my %vars = ();
if($opts{p}) {
$vars{PROXY} = $opts{p};
}
if($opts{t}) {
$vars{TIMEOUT} = $opts{t};
}
my $pinger = new Net::Ping::HTTP(%vars);
my $host = $ARGV[0];
my $packedip = gethostbyname($host);
my $ip = inet_ntoa($packedip);
print "PING HTTP $host ($ip):\n";
for(my $i = 0 ; $i < $max; $i++) {
my $start = [gettimeofday()];
my $rc = $pinger->ping("http://$host");
if ($rc < 400) {
print "Response $rc from $ip: ";
my $elapsed = tv_interval ( $start );
print "$elapsed seconds\n";
} else {
print "Response $rc from $ip: ";
print "failed\n";
}
}
sub usage {
print <<USAGE;
usage: $0 [-c number of iterations] [-p proxy] [-t timeout] [-h] hostname
where
-h : this message
-c : number of iterations to ping the host. Defaults to 1000
-p : http proxy to use for the ping
-t : timeout value. Defaults to 10 secs if none is specified
USAGE
exit(1);
}
|
Because EXE_FILES includes pingwww, some interesting events happen
during installation, which I will discuss later. The MANIFEST file must
also contain an entry for the new file. The new MANIFEST file is shown
below: Manifest, take 2
MANIFEST
Makefile.PL
lib/Net/Ping/HTTP.pm
bin/pingwww
|
Building test cases (make test)
Now that the code is written, it is time to write the test cases. Depending on your
style and the complexity of the code, it might have been worthwhile to start
with test cases, and then build the code that conforms to those tests. As stated
previously, there is more than one way to do it. Test scripts go in the t directory, as stated before. A test script is
any program that prints to STDOUT in a defined format. The first line of this
output must contain the string 1..N where N is the number of tests that will be run. Each
line after that contains either "ok" or "not ok" depending on whether the test
succeeded or failed. Test.pm
To facilitate building test scripts, there is a Test module included
with the Perl core distribution. Test provides a number of functions that
automate writing test scripts. The first is the plan function, that is used to generate the 1..N line. If your test script contained five tests,
you would have the following code begin your script: Test script header
use Test;
BEGIN { plan tests => 5}
|
Why is this easier than print "1..5\n" ? If all plan did was generate that
line, it wouldn't be. Plan , however, supports a number of additional functions
like todo lists, which will be described later. The next function of interest is the ok function. This will generate the
appropriate "ok", "not ok" output based on a number of different truth tests.
If ok() is called with one argument, it will generate "ok" if that argument
evaluates to true in Perl, and "not ok" if it evaluates to false.
In the two argument case, ok will generate "ok" if the two arguments are
equal as tested by the perl "eq" operator. This tests ASCII equivalence, so
1 will not be equal to 1.0, even though they have the same numeric value.
Ok() also has a special case when called with two arguments. The second argument
may actually be a Perl regular expression specified via either a string of the
form /pattern/ or through use of the qr( ) operator. In this case ok will
generate "ok" if the first argument satisfies the regular expression provided
by the second argument. The Test module also provides a few other features. You can specify
that certain test cases are on the "todo list". These tests won't generate
errors if they fail. If they succeed, they will generate an unexpected success
warning. This is generally a good indication that it is time to take the test
off the todo list and make it a required test. One specifies todo tests
by passing a todo array to the plan
function. The following example will set up 10 test cases, the final three of which are
on the "todo list" and hence don't have to return "ok" for the overall test
script to succeed. Test script header with todo list
use Test;
BEGIN { plan tests => 10, todo => [8,9,10] }
|
One last function provided by the Test module, is the skip() function. There are
times when a test case is not appropriate for all platforms or all configurations,
but it would be nice to run the test case if it is appropriate. The skip()
function takes three arguments. If the first one is true, the test is skipped;
otherwise, the second and third arguments are passed to ok just as documented
above. Testing Net::Ping::HTTP
There is not a great deal of function in Net::Ping::HTTP ,
so the tests will be rather easy and short. I'll create seven tests for
this module. By contrast, version 5.6.1 of the Perl interpreter specifies 12967
test cases, which are run every time the interpreter is compiled. The first test of any module is whether or not it loads at all. For this set of
tests, everything will depend on whether or not this first test succeeds.
If it doesn't, the code calls croak() (which is a nicer version of die provided by the Carp
module), as there is no reason to run the other test cases. In the
case of Net::Ping::HTTP , this can be done via the following: Simple load test case
eval { require Net::Ping::HTTP; return 1;};
ok($@,'');
croak() if $@; # If Net::Ping::HTTP didn't load... bail hard now
|
As you write more and more test cases, you will find that eval blocks are very
useful for many tests. You then
need only test to see if $@ is set to see whether or not you succeeded. In this
case if the require failed, $@ would have been set, hence the test failed. After the simple load test, the object will be tested to ensure it was instantiated
properly. This is probably overkill in this particular case, but with more complicated
modules, it is definitely a good idea. Here is the set of tests used to
check that the object is well formed: Ok test cases
my $pinger = new Net::Ping::HTTP();
ok($pinger->{TIMEOUT},10);
ok(ref($pinger->{UA}),"LWP::UserAgent");
ok($pinger->{TIMEOUT},$pinger->{UA}->timeout());
|
This tests that TIMEOUT is correctly set to the default specified, that
the LWP::UserAgent was instantiated correctly, and that the timeout value
was propagated to the object as expected. If you are installing this code on a UNIX machine, there are decent odds that
apache is running on that box. If it is there, it will most likely be running on
port 80. This can be made a skip test to try to ping localhost if apache is in the
process list. The code snippet shown will accomplish this task. Skip test case
my $count = 0;
# eval this code in case ps is not a happy camper on this platform
eval {
my @process = `ps -ef`;
$count = grep /httpd/, @process;
my $rc = $pinger->ping("http://localhost");
if ($rc >= 500) {
croak();
}
};
skip(!$count,$@,'');
|
First, the 'ps -ef' is run to snarf the process list. On a machine where ps is not a
valid command, this should return an empty array. Next, the process list is
queried for the number of httpd processes within it. Then the pinger
pings localhost on port 80, dying if a bad status code is returned.
If there are no httpd processes in the process list, the results of the
test are discounted. There are some problems with this test case, like the fact that
httpd could be running on a high port, or localhost could resolve to something
other than the local interface, but for the sake of time and space I will ignore
these. Depending on the robustness required by your program, your tests
may want to take these possibilities into account. Finally, I will add a couple of tests to ping actual sites on the Internet,
www.ibm.com and www.yahoo.com, as both of these sites are highly available.
These tests do need network
connectivity to those sites without a proxy having been specified. This environment
may not exist for many machines. Because of this, I will put these tests on the
todo list by modifying my BEGIN block. If they
work... great, if not, that's ok too. The new BEGIN block looks as follows: Final test script header
BEGIN {plan tests => 7 , todo => [6,7]};
|
The test script for Net::Ping::HTTP is now complete. It has four general
tests, one platform-specific test, and two tests marked as todo because they
are optional. After the test script has been added to the MANIFEST, it looks as follows.
MANIFEST
Makefile.PL
lib/Net/Ping/HTTP.pm
bin/pingwww
t/pingpm.t
|
A word about testing binaries
Testing binaries is a bit harder than testing modules, as you are either running
them via system() calls and just testing for exit codes, or you are running them
with back ticks and then parsing their text output. In either case, it can get
ugly fast. That is one of the many reasons why I prefer the paradigm of
placing as much core function into
modules as possible, and having scripts be little more than small wrappers
around these modules. For these reasons I am not going to build a test harness for the pingwww
program. Installation (make install)
Now the project is ready to build. I will go through the standard
build process and look at each of the steps that occur. perl Makefile.PL
The first step is running perl Makefile.PL . This will generate the Makefile
for the project. On my workstation I have installed perl 5.6.1 into /usr/bin/perl.
As I did not specify PREFIX in my Makefile.PL, this variable
will be gathered from my perl interpreter. PREFIX will be set to /usr in this
case. The pingwww program will thus be installed as /usr/bin/pingwww,
and the Net::Ping::HTTP module will be installed as /usr/lib/perl5/site_perl/5.6.1/Net/Ping/HTTP.pm. The PREFIX variable, as well as many other MakeMaker variables can be
overridden both in the Makefile.PL as well as on the command line. The command
line syntax for such variable assignment is perl Makefile.PL VARIABLE=VALUE .
For instance, if you wished pingwww to install as /usr/local/bin/pingwww, you could
override PREFIX as such: Command line arguments to Makefile.PL
perl Makefile.PL PREFIX=/usr/local
|
This would put the binaries in /usr/local/bin, and the modules in /usr/local/lib/perl5/site_perl/5.6.1.
Although /usr/local/bin is probably in your path, /usr/local/lib/perl5/site_perl/5.6.1 may not
be in your Perl include path (unless perl is really in /usr/local/bin), so changing
PREFIX indiscriminately is not advised. Here is the expected output of perl Makefile.PL : perl Makefile.PL output
rigel:~/NetPingHTTP> perl Makefile.PL
Checking if your kit is complete...
Looks good
Writing Makefile for Net::Ping::HTTP
|
MakeMaker has checked to make sure that you have all the files that MANIFEST
says you should have, and that it can find all the modules that you have listed in
PREREQ_PM. make
The next thing to do is run make . This will build all of the perl binaries and
libraries into a temporary blib directory. From there, test and installation
will occur. If you have taken the time to write proper POD documentation in your binaries
and modules, you will gain the benefit of automatic documentation generation.
On a UNIX machine, the make
process will automatically generate man pages for all the binaries and modules
that have POD markup in them. As I said before, this lets you embed all your
documentation next to the code that it describes, and maintain only one file. As of perl 5.6.0 you can have MakeMaker build HTML documents for your modules as well.
If you define INSTALLHTMLSITELIBDIR either on the perl Makefile.PL command line, or
in Makefile.PL, HTML documents will be installed in that directory during installation.
I would like HTML documentation generated for me, so I will rerun perl Makefile.PL as follows: Generating HTML documentation
perl Makefile.PL INSTALLHTMLSITELIBDIR=~/public_html
|
This will build the HTML docs in my personal Web directory. Another useful thing that MakeMaker does during build is fix the
shebang line in all of your perl binaries to whatever is appropriate in your
environment. This means I don't have to worry that whoever is installing my
code might have perl located in /opt/fromsource/bin/perl or some other
odd location, and because of that the program will not run. Running make produces the following output: make output
rigel:~/NetPingHTTP> make
cp lib/Net/Ping/HTTP.pm blib/lib/Net/Ping/HTTP.pm
cp bin/pingwww blib/script/pingwww
/usr/bin/perl -I/usr/lib/perl5/5.6.1/i686-linux -I/usr/lib/perl5/5.6.1
-MExtUtils::MakeMaker -e "MY->fixin(shift)" blib/script/pingwww
Htmlifying blib/html/lib/lib/Net/Ping/HTTP.html
/usr/bin/pod2html: lib/Net/Ping/HTTP.pm: cannot resolve L<perl> in paragraph 13. at
/usr/lib/perl5/5.6.1/Pod/Html.pm line 1562.
Manifying blib/man3/Net::Ping::HTTP.3
Manifying blib/man1/pingwww.1
|
One warning was generated during the make. This is because I used the LE<lt>E<gt> syntax
in my POD documentation, which specifies a reference to an external entity. Pod::HTML
has some problems generating arbitrary external links, so you will often see warnings
at this stage. In general you need not worry about any errors generated by pod2html,
as they have little impact on your project. make test
The next stepp is executing the test phase of the project. You should expect
output as follows: make test output
rigel:~/NetPingHTTP> make test
PERL_DL_NONLAZY=1 /usr/bin/perl -Iblib/arch -Iblib/lib
-I/usr/lib/perl5/5.6.1/i686-linux -I/usr/lib/perl5/5.6.1 -e
'use Test::Harness qw(&runtests $verbose); $verbose=0;
runtests @ARGV;' t/*.t
t/pingpm............ok, 1/7 skipped: unknown reason
All tests successful, 1 subtest skipped.
Files=1, Tests=7, 20 wallclock secs ( 0.36 cusr + 0.03 csys = 0.39 CPU)
|
I did not have apache running on my machine when I ran the tests, so one
test, test number 5, was skipped. You can see that Test::Harness also runs timing code during the test process.
It keeps track of both "wallclock secs", which is the elapsed time from the
start to the end of the tests, and CPU time, which is the total processing
time on the CPU. The reason the wallclock time for these tests was 20 seconds,
when the CPU time was less than one second, is that in my environment there is
a firewall between my workstation and www.ibm.com and www.yahoo.com. The LWP::UserAgent
that is instantiated by Net::Ping::HTTP was created with a 10-second timeout, and
hence had 2 timeouts before it moved on. Because I specified tests 6 and 7
as todo tests, no errors were generated due to these failures. To illustrate, let's run make test without asserting that tests 6 and 7
are todo tests. This will generate failures in my environment.
As you first begin to use test cases, you will see more failures than successes,
so it is good to know how to interpret them: make test failure output
rigel:~/NetPingHTTP> make test
PERL_DL_NONLAZY=1 /usr/bin/perl -Iblib/arch -Iblib/lib
-I/usr/lib/perl5/5.6.1/i686-linux -I/usr/lib/perl5/5.6.1 -e
'use Test::Harness qw(&runtests $verbose); $verbose=0;
runtests @ARGV;' t/*.t
t/pingpm............FAILED tests 6-7
Failed 2/7 tests, 71.43% okay (-1 skipped test: 4 okay, 57.14%)
Failed Test Status Wstat Total Fail Failed List of Failed
--------------------------------------------------------------------------------
t/pingpm.t 7 2 28.57% 6-7
1 subtest skipped.
Failed 1/1 test scripts, 0.00% okay. 2/7 subtests failed, 71.43% okay.
make: *** [test_dynamic] Error 29
|
The output is slightly busy, but the meaning is clear.
The pingpm test script failed tests 6 and 7. This fact is presented
in multiple places in the output. The results also provide statistical analysis of the
number of tests that succeeded and failed. If you had created multiple test scripts, the
final "Failed" line would tell you how many of your test scripts were not 100% successful, as
well as how many total test cases failed. make install
The last part of building the project is running make install . If you are
installing into privileged directories (like /usr), you will need to be root
to do the install. At this point all binaries, modules, man pages, and Web pages
will be installed. make install output
[root@rigel]# make install
Installing /home/sdague/public_html/lib/Net/Ping/HTTP.html
Installing /usr/man/man1/pingwww.1
Installing /usr/man/man3/Net::Ping::HTTP.3
Installing /usr/bin/pingwww
Writing /usr/lib/perl5/site_perl/5.6.1/i686-linux/auto/Net/Ping/HTTP/.packlist
Appending installation info to /usr/lib/perl5/5.6.1/i686-linux/perllocal.pod
|
Now the code is installed, and the build process is completed. Distributing your code (make dist)
If you follow this procedure for your project, your will end up with
a nice program that is easily installable by others. If you wish to
distribute that code, MakeMaker provides a make dist command
that will build a tar.gz distribution of the project (all the files listed in the
Manifest) named "project-version.tar.gz". You can then easily put this
file up for distribution. Before you do that, there are a few other documents that are usually distributed
with perl code. README
This file is a brief overview of what the project is about, and what it gives you. COPYING (or LICENSE)
This file describes what license the files are under, and what restrictions
are placed on the user to copy, modify, or redistribute the code. Many perl
modules are "Licensed under the same terms as Perl itself", which means dual
licensing under the GNU Public License and the Artistic License. You may,
however, choose any license you feel appropriate for your code. Changes
This is a change log of what has been added in each release of the file. This is really
a good thing to keep up to date as it helps other people figure out what
features were added, and when they became part of the main distribution. TODO
This is a todo list of future bug fixes and features. It is always good idea to include
such a file with your distribution. It provides your users information about the direction
the project is headed in. You never know, but some adventurous user may even decide
to implement features off your Todo list and send them to you. Just remember, make sure to add any additional files to your MANIFEST, or
they will not be bundled when you run make dist . If your code is reusable enough that others find it useful, you should
consider submitting it to CPAN (see Resources).
Using the methodology described here, your code will be very close to compliance
with the CPAN modules format. Conclusion
I have only touched the tip of the iceberg here. Beyond just pure Perl projects,
you can also manage C & Perl joint projects under this infrastructure. The
infrastructure is built in Perl, which means that it is extremely portable, running
on platforms ranging from Linux to Windows to S/390. Once you get used to this
infrastructure, you will find it totally invaluable for all the projects you work on. You
will never have to write an install script again, and through the use of well formed
test cases, you can have a far higher level of confidence that your program is
performing the way it was intended. Resources About the author Sean Dague is a Software Engineer at the IBM Linux Technology Center in Poughkeepsie, New York.
He currently is working on a number of open source projects, including System
Installation Suite, a Linux installation and maintenance tool, and OSCAR, a
tool for building Linux high-performance computing clusters. He can be reached at japh@us.ibm.com. |

|