How to make CPAN-like module

Perl modules are distributed in form of .tar.gz. In this form they can be uploaded to CPAN or used with wide range of ready made open source utilities like for example dh-make-perl that will make debian package out of them. This form makes the modules organization and instalation really easy.

For a "quick start" the most easiest way is to use Module::Starter. So either do apt-get install libmodule-starter-perl or cpan -i Module::Starter. This will add a module-starter script to the system, which can create the whole file structure for us. For example we want to make a module representing train. ;-) Don't ask me why. (nothing more clever camed to my mind)

$ module-starter --builder=Module::Build --module=Acme::DopravnyProstriedok::Vlak \
                 --author="Jozef Kutej" --email=jozef@kutej.net
Created starter directories and files
$ tree Acme-DopravnyProstriedok-Vlak/
Acme-DopravnyProstriedok-Vlak/
|-- Build.PL
|-- Changes
|-- MANIFEST
|-- README
|-- lib
|   `-- Acme
|       `-- DopravnyProstriedok
|           `-- Vlak.pm
`-- t
    |-- 00-load.t
    |-- boilerplate.t
    |-- pod-coverage.t
    `-- pod.t

What is a meaning of the files:

Build.PL

use strict;
use warnings;
use Module::Build;

my $builder = Module::Build->new(
    module_name         => 'Acme::DopravnyProstriedok::Vlak',
    license             => 'perl',
    dist_author         => 'Jozef Kutej <jozef@kutej.net>',
    dist_version_from   => 'lib/Acme/DopravnyProstriedok/Vlak.pm',
    build_requires => {
        'Test::More' => 0,
    },
    add_to_cleanup      => [ 'Acme-DopravnyProstriedok-Vlak-*' ],
    create_makefile_pl => 'traditional',
);

$builder->create_build_script();

Build.PL using `perl Build.PL` command will create a file called "Build" that is it self a Perl script and is executable. Build has many arguments:

$ ./Build help

 Usage: ./Build <action> arg1=value arg2=value ...
 Example: ./Build test verbose=1
 
 Actions defined:
  build                          manifest    
  clean                          manpages    
  code                           pardist     
  config_data                    ppd         
  diff                           ppmdist     
  dist                           prereq_report
  distcheck                      pure_install
  distclean                      realclean   
  distdir                        retest      
  distmeta                       skipcheck   
  distsign                       test        
  disttest                       testall     
  docs                           testcover   
  fakeinstall                    testdb      
  help                           testpod     
  html                           testpodcoverage
  install                        versioninstall

Run `Build help <action>` for details on an individual action.
See `perldoc Module::Build` for complete documentation.

The most importat are - build, test, install, clean. "build" will prepare all files necessary for a module instalation by copying them to the blib folder. "test" will run all the test scripts that can be found in t/ folder. "install" will install all the files to the system. And "clean" will cleanup files after previous Build executions.

Common guide to install a module is `perl Build.PL && ./Build && ./Build test && ./Build install`, that is in case that the instalation (like this example one) is based on Module::Build. Second options is ExtUtils::MakeMaker. In this case the instalation procedure is `perl Makefile.PL && make && make test && make install`. If there is create_makefile_pl => 'traditional' in the Build.PL then Module::Build will create Makefile.PL in the time of creating distribution. A wars are fought if to use Module::Build or ExtUtils::MakeMaker and of course those are not all options. There is also Module::Install and ...

Changes

Revision history for Acme-DopravnyProstriedok-Vlak

0.01    Date/time
        First version, released on an unsuspecting world.

Changes holds changes that occure between version releases.

MANIFEST

Build.PL
Changes
MANIFEST
README
lib/Acme/DopravnyProstriedok/Vlak.pm
t/00-load.t
t/pod-coverage.t
t/pod.t

MANIFEST holds a list of files which belong to the module. The rest of the files will be ignored at the time a distribution (Acme-DopravnyProstriedok-Vlak-0.01.tar.gz) is created. MANIFEST should be kept up to date. It's easy to add a file to a folder and forget to update MANIFEST. As a helper there is `./Build distcheck` that will "complain" if there is some extra file.

It's good to add MANIFEST.SKIP where is the list of files that should be really ignored. (mostly the extra files autogenerated by `Build`)

MANIFEST.SKIP

# Avoid version control files.
\bRCS\b
\bCVS\b
,v$
\B\.svn\b

# Avoid Makemaker generated and utility files.
\bMakefile$
\bblib
\bMakeMaker-\d
\bpm_to_blib$
\bblibdirs$
^MANIFEST\.SKIP$

# Avoid Module::Build generated and utility files.
\bBuild$
\b_build
\bcover_db

# Avoid temp and backup files.
~$
\.tmp$
\.old$
\.bak$
\#$
\b\.#
\.cvsignore
\.svnignore

README

When there is create_readme => 1, in the Build.PL, then README file is autogenerated from the POD of the main .pm file. In our case lib/Acme/DopravnyProstriedok/Vlak.pm. This update will happend by `./Build dist` or by `./Build distmeta`.

META.yml

This file holds basic information about the module in YAML format. It is automaticaly generated by `./Build dist` or by `./Build distmeta` from the information found in Build.PL .

.cvsignore

Holds examples what can be ignored when using revisioning control system for module development. In this for can be directly used by CVS. Subversion users can do `svn propset svn:ignore "`cat .cvsignore`" . && svn rm .cvsignore` or GIT users `mv .cvsignore .gitignore`.

t/ folder

A test scripts are in t/ folder. Every good module/distribution has a couple of them. They will guarranty as during the development time, but mainly when fixing or refactoring code that the functionality stay unchanged. Also thanks to these test most modules are high quality. When author upload new module version to the CPAN, this is automaticaly tracked by smoke testing servers that will run these tests. The output are the reports about passed/failed tests for different versions of Perl and different operating systems. This reports can then be see along other module information.

t/00-load.t is a basic test that will check if the module compiles. t/pod-coverage.t will go through the code and check if all functions has a pod documentation. t/pod.t will check if the pod documentation is without errors. It's good to add also following tests:

00-compile.t checks if all modules in the folder structure will compile. -

use strict;
use Test::More;

eval "use Test::Compile 0.08";
plan skip_all => "Test::Compile 0.08 required for testing compilation"
    if $@;

my @pmdirs = qw(lib blib sbin bin);
all_pm_files_ok(all_pm_files(@pmdirs));

pod-spell.t checks pod spelling -

use strict;
use Test::More;

if (!$ENV{TEST_SPELLING}) {
    plan skip_all =>
      "Set the environment variable TEST_SPELLING to enable this test.";
}

eval 'use Test::Spelling;';

plan skip_all => "Test::Spelling required for testing POD spelling"
    if $@;

add_stopwords(qw(
    Jozef
));
all_pod_files_spelling_ok();

distribution.t checks if all distribution files are ok -

use Test::More;

eval 'use Test::Distribution not => "sig"';
plan( skip_all => 'Test::Distribution not installed') if $@;

signature.t checks digital signature of the module author. This test will run only if $ENV{TEST_SIGNATURE} will be set and if SIGNATURE file is present. SIGNATURE can be automaticaly generated by Build.PL by adding sign => 1, -

#!/usr/bin/perl

use strict;
use Test::More;

if (!$ENV{TEST_SIGNATURE}) {
    plan skip_all =>
      "Set the environment variable TEST_SIGNATURE to enable this test.";
}
elsif (!eval { require Module::Signature; 1 }) {
    plan skip_all =>
      "Next time around, consider installing Module::Signature, ".
      "so you can verify the integrity of this distribution.";
}
elsif ( !-e 'SIGNATURE' ) {
    plan skip_all => "SIGNATURE not found";
}
elsif ( -s 'SIGNATURE' == 0 ) {
    plan skip_all => "SIGNATURE file empty";
}
elsif (!eval { require Socket; Socket::inet_aton('pgp.mit.edu') }) {
    plan skip_all => "Cannot connect to the keyserver to check module ".
                     "signature";
}
else {
    plan tests => 1;
}

my $ret = Module::Signature::verify();
SKIP: {
    skip "Module::Signature cannot verify", 1
      if $ret eq Module::Signature::CANNOT_VERIFY();

    cmp_ok $ret, '==', Module::Signature::SIGNATURE_OK(), "Valid signature";
}

lib/Acme/DopravnyProstriedok/Vlak.pm

Finaly we are at the purpouse of doing all this parade :-) , and that is to create a module for a general purpouse. Here everyone has a free hand and can let his fantazy fly. What it is a module and how does it look inside will be a scope of a differen tutorial.

Resume

Quick start:

module-starter --builder=Module::Build --module=Acme::DopravnyProstriedok::Vlak::Rychlik \
                 --author="Jozef Kutej" --email=jozef@kutej.net
cp MANIFEST.SKIP Acme-DopravnyProstriedok-Vlak-Rychlik/
cp 00-compile distribution.t pod-spell.t  signature.t \
	Acme-DopravnyProstriedok-Vlak-Rychlik/t/
rm Acme-DopravnyProstriedok-Vlak-Rychlik/t/boilerplate.t
cd Acme-DopravnyProstriedok-Vlak-Rychlik/
vim Build.PL 
# pridat
#    create_readme       => 1,
#    sign                => 1,
vim lib/Acme/DopravnyProstriedok/Vlak/Rychlik.pm
# code something that makes sence
# change version in .pm, fill out the Changes
perl Build.PL; ./Build clean; perl Build.PL && ./Build distcheck && ./Build disttest && ./Build dist
# upload => pause.cpan.org

Word at the end. Don't worry what you upload to CPAN too much. If it's not coliding with a different module it will be indexed and will stay for ever. Even it is good to respekt some namespaces like DateTime that try to have the house tidy, but in those case you'll be contacted that you should stop doing the mess... ;-)

Links: CPAN - Comprehensive Perl Archive Network

24. Jun 2008
Jozef