IBM Skip to main content
Search for:   within 
    Search help  
    IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Linux | Open source projects
developerWorks
Building Perl projects with MakeMaker
97KBe-mail it!
Contents:
Programming products vs. programs
Anatomy of a MakeMaker project
Your first MakeMaker project (make)
Building test cases (make test)
Installation (make install)
Distributing your code (make dist)
Conclusion
Resources
About the author
Rate this article
Related content:
Application configuration with Perl
Debugging Perl with ease
The elegance of JAPH
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
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.


97KBe-mail it!

What do you think of this document?
Killer! (5)Good stuff (4)So-so; not bad (3)Needs work (2)Lame! (1)

Comments?



developerWorks > Linux | Open source projects
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact