README
This project is yet another attempt to create a repeatable, easy to use script for creating CPAN distributions.
Table of Contents
- README
- Overview
- Quick Start
- More Details
- Perl Dependencies
- What Next?
- Creating a CPAN Distribution
- FAQ
- Why is this easier than just building a
Makefile.PL
by usingExtUtils::MakeMaker
? - Why is there a bash script AND a Perl script
- Why did you autoconfiscate a project that has just 1 Perl script and 1 bash script
- Where do files in the
extra_files
section end up? - How can I add some post installation operations?
- Why is this easier than just building a
- Module Versions
- Finally
- Author
Overview
Historically, because I have been using the Redhat Package Manager to create RPMs of Perl modules I install as part of my application stacks, I don't bother with the creation of CPAN distributions.
In order to possibly share some of these modules and to use a more
modern Perl toolchain (cpanm
) to vendor libraries, I've needed a
quick and easy CPAN distribution creation utility compatible with my
existing toolchain. Hence this project.
You can read more about this utility here. Or after installing the project:
make-cpan-dist.pl -h
perldoc CPAN::Maker
Quick Start
cpanm -v CPAN::Maker
make-cpan-dist.pl -h
If you want to install this by building the project make sure you have
the autotools
toolchain installed (autoconf
and automake
). If
you are using a RedHat derived Linux distribution, install the
autoconf
and automake
packages using yum
. If you are using a
Debian based system then you may have success using apt
to install
the necessary dependencies. There are also some Perl module
dependencies that are checked when you run ./configure
.
The build
script in the root directory attempts to build the
software for several different Linux distro flavors.
git clone https://github.com/rlauer6/make-cpan-dist
cd make-cpan-dist
./build
If you want to do this in pieces take a look at the build script.
The build script essentially does the following after installing dependencies:
./bootstrap
./configure
make & make install
HINT: If you want to install locally, set --prefix
during the
configure process or update the build
script.
./configure --prefix=$HOME/local
make && make install
More Details
The goal of the project is to take a set of Perl modules, scripts and possibly (hopefully) tests and automatically create a CPAN distribution. The automatic part is key, as I'd like this to simply be part of a CI/CD pipeline for various projects. The idea would be to create and include in the project a build specification file for creating CPAN distributions.
To be clear, this utility will not do everything you can do by using
ExtUtils::MakeMaker
and passing it all of the appropriate
arguments. It does enough though to be a very useful
tool for automating your builds and creating a CPAN tarball.
If you run into limitations or bugs I'd appreciate an issue being opened to let me know why and possibly how I might add new features to make it more applicable to a wider set of scenarios. As the kids say, pull requests are welcome too.
Perl Dependencies
One of the challenging aspects of developing Perl applications is packaging and deploying your code. Part of that process involves identifying all of the Perl modules you've installed from CPAN to use in your application. Once you've identified all of the artifacts necessary to include in your package (so your application will run somewhere other than your laptop) you need to package those up somehow. At the very least you need to create a manifest that can be used when your application is installed.
Identifying non-core Perl modules required by your application can be done manually by:
- inspecting each Perl module and finding where you
use
orrequire
a module. - determining the version of that module that has been tested with your application
- determining if that module version was core in the Perl version your application will running on or it needs to be installed
perl
provides a utility (corelist
) which will report the version
of perl
that a particlular module was added or removed from core.
Make sure you have an updated version of corelist
installed!
This project uses the scandeps-static.pl
utility to resolve
dependencies. this utility is distributed with the
Module::ScanDeps::Static
Perl module. It is a rewrite of perl.req
found in RedHat systems.
I've found it to do a better job than scandeps.pl
or any of the
other dependency resolvers you might stumble across. That's not to
say that it is fool proof or even the best dependency checker for
Perl. That is a subject of a long blog post I think I should write
some day. Oh wait I did...
If you don't want to use that utility but have another favorite Perl
module dependency resolver then you're free to use that by providing
it on the command line (-r) of the bash
helper script
(make-cpan-dist
) or in the build specification. The function you
specify should simply provide a list of Perl modules one per line and
output that to STDOUT.
You can replace the use of scandeps-static.pl
with scandeps.pl
by
specifying the -s
option to the helper script.
Other competing dependency checkers include:
Devel::Modlist
Perl::PrereqScanner
...all of which will give about the same results as scandeps.pl
If you use any of those other checkers, wrap them in a script to make sure they output the list in the correct format.
cat <<eof > dep_resolver
#!/bin/bash
perl -MDevel::Modlist=nocore \$1.pm 2>&1 | awk '{ print \$1}'
eof
chmod +x dep_resolver
make-cpan-dist -a 'Rob Lauer <rlauer6@comcast.net>' \
-m MyFunc -R no -l . -d "my function" \
-r dep_resolver
What Next?
After successfully building and installing the project you will have available two utilities that are used together to build a CPAN distribution.
make-cpan-dist
make-cpan-dist.pl
When using a buildspec file you need only be concerned with
make-cpan-dist.pl
and simply pass the buildspec file as a parameter.
make-cpan-dist.pl -b buildspec.yml
Creating a CPAN Distribution
There are three possible ways to create a CPAN distribution using the
utiities contained in this project, each with varying degrees of
simplicity and flexibility. As a reminder, the point of these
utilities is to provide a simple solution right? So the
simplest thing you can do is run the utility against a
buildspec.yml
file that describes the distribution you would like to
create.
The Easy Way
Create a buildspec.yml
file that looks something like this:
project:
git: https://github.com/rlauer6/perl-Amazon-Credentials.git
description: "AWS credentials discoverer"
author:
name: Rob Lauer
mailto: rlauer6@comcast.net
pm_module: Amazon::Credentials
path:
pm_module: src/main/perl/lib
tests: src/main/perl/t
exe_files: src/main/perl/bin
You specify some project metadata in the project:
section and
possibly a pointer to the project in a git repository that will be
cloned.
You must also provide the name of the Perl module to package
(pm_module
) and include a path:
section that will point to the
modules and artifacts to be packaged.
The path
attributes specify the path to the module, the path to the
tests and the path to executable scripts which will be included in
the distribution. All files with an extension of .t
are assumed to
be included in the package if you have specified a test path.
If your project includes other Perl modules somewhere in the Perl module path then they will be packaged as well. Paths are relative to the root of the project or your current working directory if you are not specifying a git repository as the source of your package.
If your project includes Perl scripts, you can add those to your
distribution by setting the path to those with the exe_files
and
scripts
subsection of path
. These will be packaged and installed
in the bin
directory (INSTALLBIN).
So, assuming you have created an appropriate
buildspec.yml
file, the easy way boils down to this:
make-cpan-dist.pl -b buildspec.yml
After executing that statement, you should have a tar ball in your current working directory.
Additional Build Specification Options
The build specification file can contain some additional options to control what and how things get packaged.
-
use the
recurse
option to add additional Perl modules from your project path. If you want to add additional Perl modules to the distribution, just make sure they are under the directory path of your core module and therecurse
option is set toyes
. Actually this is the default. If you don't want to recurse, set this to no.path: recurse: yes
-
to use a different module dependency checker than the default (
scandeps-static.pl
) set theresolver
option under thedependencies
section. A value ofscandeps
will usescandeps.pl
or set the name of an executable that will simply output a list of Perl module names.dependencies: resolver: scandeps
-
to manually specify a list of dependencies, set the
requires
option under thedependencies
section to the path to a file that contains a list of Perl modules. If the name of the file iscpanfile
then it is assumed to be acpanfile
formatted list, otherwise the list should be a simple listing of module names optionally followed by a version (e.g.List::Util 1.5
). By default core modules will be filtered from the list of modules.Include the option
core_modules
with a value of yes if you do not want to filter out core modules.min_perl_version: 5.16.3 dependencies: requires: requires core_modules: yes
If you filter out core modules be aware that you may still require a specific version of a module that is core. Take
List::Util
for example. This module has been in core since v5.7.3, however the functionszip
andmesh
were not added until March of 2021. If you were running on a system that installed only the core modules for v5.16.3 you would find that the version ofList::Util
is 1.27 and does not include those functions.So, if you want to filter out core modules but need to include a core module with a specific version, place a + in front of the module name (e.g.
+List::Util 1.5
) in yourrequires
file.You should also include the minimum version of
perl
that is going to be used to determine if a module is core. The default is $PERL_VERSION (the version of perl in your environment) which may not be the same version as your target deployment environment!
The Harder Way
A slighly harder way is to call the helper bash script directly with specific options to create the distribution.
usage: make-cpan-dist Options
Utility to create a CPAN distribution. See 'man make-cpan-dist'
Options
-------
-a author - author (ex: Anonymouse <anonymouse@example.org>)
-b buildspec - use a buildspec file instead of options
-d description - description to be included CPAN
-D file - use file as the dependency list
-e path - path for extra .pl files to package
-h - help
-f file - file containing a list of extra files to include
-l path - path to Perl modules
-m name - module name
-o dir - output directory (default: current directory)
-p - preserve Makefile.PL
-P file - file that contains a list of modules to be packaged
-r pgm - script or program to list dependencies
-s - use scandeps.pl to find dependncies
-R yes/no - recurse directories for files to package (default: yes)
-t path - path to test files
-v - more verbose output
-V - version from
-x - do not cleanup files
NOCLEANUP=1, PRESERVE_MAKEFILE=1 can also be passed as environment variables.
Example:
Assuming your source tree looks something like this:
.
lib/
lib/Foo
lib/Foo/Bar.pm
t/
...then...
make-cpan-dist -l lib -t t -m Foo::Bar
So far, neither of these methods is particularly hard and can be used
interchangeably as part of your deployment pipeline. The key to the
"easiness" part, at least for me, is the fact that the bash
script
will try to resolve dependencies using the selected dependency resolver
and find the Perl module version for each of those
dependencies. The dependency resolver is not perfect,
specifically it may get tripped up on some ways your clever Perl
module utilizes other resources. In general though, it's good
enough. You you can always ask the script to preserve (-p
) the
Makefile.PL
that is generated and start tweaking that yourself. You
could also open an issue and I'll try to tackle it. You could also
make a pull request. ;-)
The Hardest Way
The hardest way to create a CPAN distribution using this utility is to
provide the dependency files for your module by creating them yourself
and then calling make-cpan-dist.pl
directly to create a
Makefile.PL
file that you can then modify.
The dependency files list the module name and their version (or 0). For example:
Amazon::Signature4 1.02
At this point you have pretty much decided to roll your own
Makefile.PL
so have fun with ExtUtils::MakeMaker
.
To summarize what the bash
helper script does, all of which you can
of course do manually, the script will:
- iterate over all of the
.pm
files in your source tree- run a dependency checker and save the output
- sort the list and get the unique dependencies
- separate out the dependencies from the modules provided by your project
- iterate over the sorted list of dependencies
- retrieve the version number of each module
- save the module and version number to the dependency file
- repeat for each test (*.t) in the test directory
- repeat for the each script in your script directory
The result of the above operation is a set of files:
requires
test-requires
provides
These files are eventually used by the make-cpan-dist.pl
script to
create your final Makefile.PL
.
FAQ
Why is this easier than just building a Makefile.PL
by using ExtUtils::MakeMaker
?
Well, to be upfront about it, maybe it's not, especially for
smaller projects. Using the buildspec approach does make it
particulary easy though. And as your project grows modifying the
buildspec file is probably easier than remembering how
ExtUtils::MakeMaker
works.
Moreover, I feel this approach makes automation easier. I alway find
it best to take a bunch of steps I will seldom remember and package
them into self-contained utilities that can be forgotten about. This
approach works for me, but as always YMMV. As I mentioned, I'm also
using this utility as a component of a CI/CD pipeline. Here's an
example of using this utility with a buildspec
file in a Makefile
.
For a real simple project whose file hierarchy looks like this:
ChangeLog
README.md
lib/Foo/Bar.pm
lib/Foo/Bar/Baz.pm
bin/foo.pl
t/00-foo.t
...your buildspec file might look like this:
project:
description: "My awesome project"
author:
name: Your Name
mailto: anonymouse@example.org
pm_module: Foo::Bar
extra-files:
- README.md
- ChangeLog
path:
pm_module: lib
tests: t
exe_files: bin
..and your Makefile
like this:
VERSION := $(shell perl -I lib -MFoo::Bar -e 'print "$$Foo::Bar::VERSION";')
PROJECT=Foo-Bar-$(VERSION).tar.gz
MODULES = \
lib/Foo/Bar.pm \
lib/Foo/Bar/Baz.pm
SCRIPTS = \
bin/foo.pl
$(PROJECT): buildspec.yml $(MODULES) $(SCRIPTS)
make-cpan-dist.pl -b $<
CLEANFILES = \
$(PROJECT)
clean:
for a in $(CLEANFILES); do \
rm -f "$(PROJECT)"; \
done
...which could also be accomplished from the command line:
make-cpan-dist -l lib -S bin -t t -m Foo::Bar
By creating a Makefile
recipe, whenever I update the buildspec or
any of the modules or scripts and run make
, I'll automatically
create a new distribution.
Why is there a bash script AND a Perl script
If you find yourself scratching your head and wondering if
indeed the bash
script calls the Perl script and vice versa, you are
correct.
The Perl script has two purposes:
- parse the buildspec into options that are sent to the bash script
- write a
Makefile.PL
based on the options sent to it by the bash script
The bash script finds your artifacts to be packaged, Perl module
dependencies and calls the Perl script to write the Makefile.PL
. In
the beginning I think I created a bash script that did all of the
heavy lifting but later found I needed a more powerful environment for
enhancing the script.
Why did you autoconfiscate a project that has just 1 Perl script and 1 bash script
Before we go any further, I'll bet I need to answer that question. So, in no particular order...
- Habit
- Automation malleability
- Familiarity with the toolchain
- Standardization of my development process
- Flexibility to add more automation with
make
as a project organically matures - Ubiquity of the toolchain
- Potential portability (nothing is 100% portable, but we can try)
I also leverage autoconfiscation templates to create things like man
pages from Perl scripts and of course the installation process is made
simpler when you can rely on some degree of portability and
standardization of your toolchain. Many disagree and hate autoconf
-
I get it - but it's not a holy war.
Take a look at autoconf-template-perl if you are curious about how to autoconfiscate a Perl project.
Where do files in the extra_files
section end up?
Files in the extra-files
section of the buildspec.yml
file or in
the extra-files
file are packaged as part of the distribution
tarball but will not be installed _unless you add them to the share:
section beneath extra-files
. In that case they will be installed
relative to the distribution's share directory.
extra-files:
- share:
- ChangeLog
- README.md
You can find out where the distribution's share directory is as shown below.
perl -MFile::ShareDir=dist_dir -e 'print dist_dir("Foo"),"\n";'
How can I add some post installation operations?
ExtUtils::MakeMaker
provides the capability to add an additional step to your build or
installation process by providing a postamble
section. make-cpan-dist.pl
supports this by allowing you to add a
section to your buildspec.yml
file which specifies the name of a
file containing the extra make
instructions that will be executed
after the build and before the installation. A typical postamble file
might look like this:
postamble ::
.PHONY: FOO
FOO:
echo "Thanks for using FOO!"
install:: FOO
Module Versions
It's sometimes convenient to keep a project's version in a file other
than main Perl module. You might do this for example if you have
several modules in the distribution and you want them all to reflect
the same version number. If you decid to keep your project's version
in a file other than the main module, you can specify the -V
option
to the bash script or add version_from:
in your buildspec.
package Foo::Bar::VERSION;
our $VERSION = '1.0.1';
1;
package Foo::Bar;'
require Foo:Bar::VERSION;
1;
...then
make-cpan-dist -l lib -t t -S bin -m Foo::Bar -V Foo::Bar::VERSION
Finally
I hope you find this useful. If I can make it more useful, let me know.
Author
Rob Lauer rlauer6@comcast.net