Table of Contents
- NAME
- SYNOPSIS
- DESCRIPTION
- QUICK START
- WHY YOU SHOULD CONSIDER USING CPAN::Maker::Bootstrapper
- IMPORTING FILES
- CONFIGURATION
- INSTALLED PROJECT FILES
- THE PROJECT MAKEFILE
- COMMANDS
- OPTIONS
- THE REVIEW WORKFLOW
- PROMPT PROFILES
- EXTENDING THE BUILD SYSTEM
- MODULINOS
- PREREQUISITES
- CAVEATS
- FAQ
- My build is failing with a module not found error during syntax
- How do I do a fast build during development?
- How do I add a new module or script to the project?
- How do I include additional files in the distribution?
- I want to pin a version or add a module the scanner missed
- I want to exclude a module the scanner found
- I edited a .pm file and my changes disappeared
- Why does my build say it has drifted from the installed bootstrapper?
- make says nothing to do but my source changed
- How do I disable scanning temporarily?
- How do I disable syntax checking temporarily?
- How do I upgrade the build system?
- I want to add a bash script to my distribution
- What is
make release-notesused for? - Can I distribute the POD in my modules separately?
- The dependency resolver keeps adding a file I don't want to
- Something still doesn't work - how do I report an issue?
- SEE ALSO
- DEPENDENCIES
- VERSION
- AUTHOR
- LICENSE
NAME
CPAN::Maker::Bootstrapper - Scaffold a new CPAN distribution in one command
SYNOPSIS
# Create a configuration file (recommended first-time setup)
cmb create-config > ~/.cpan-makerrc
export CPAN_MAKER_CONFIG=$HOME/.cpan-makerrc
# Create a new plain Perl module project
cmb --module My::New::Module
# Create a CLI module project (inherits from CLI::Simple)
cmb --module My::New::CLI --stub cli
# Use a custom stub
cmb --module My::Module --stub /path/to/mystub.pm
# Import files from another project
cmb --module My::Module \
-I /path/to/my-module/lib -I /path/to/my-module/bin \
--installdir /tmp/My-Module
# Install into a specific directory
cmb --module My::Module --installdir ~/git/My-Module
# Override git identity
cmb --module My::Module --username "Rob Lauer" --email rob@example.org
# Run a code review on a module (set API key in environment)
export LLM_API_KEY=$(cat ~/.ssh/anthropic-api-key)
cmb code-review lib/My/Module.pm
DESCRIPTION
CPAN::Maker::Bootstrapper scaffolds a new Perl project that
leverages CPAN::Maker to create a ready-built CPAN distribution. It
installs a project Makefile, a buildspec.yml pre-populated and
ready to feed to CPAN::Maker, optional stub source file and test
file, and supporting makefile snippets that implement the bootstrapper
framework. It then runs make to generate your distribution tarball.
The result is a project that can produce a distributable tarball with
a single make invocation without hand-editing build configuration
for standard project layouts.
CPAN::Maker::Bootstrapper also provides AI-assisted development
tools via the Anthropic Claude API. These include iterative code
review with structured finding annotations, POD documentation review,
optional POD generation, and AI-generated release notes. See "LLM
Commands" and "THE REVIEW WORKFLOW" for details.
NOTE: Check out the release-notes directory in the GitHub project for examples of release notes generated by the LLM.
QUICK START
Install the bootstrapper and its dependencies:
cpanm CPAN::Maker::Bootstrapper
Note: Before scaffolding your first project, consider running create-config
to set up a personal configuration file - it pre-populates your git
identity, GitHub username, and preferred project directory so you never
have to pass them on the command line. See "CONFIGURATION" for details.
Scaffold a new project:
cmb --module My::Module --installdir ~/git/My-Module
The bootstrapper creates the project directory, installs the build
system, generates stub source and test files, and runs make
automatically. By the time it finishes you already have a working
distribution tarball in ~/git/My-Module.
By default the final build step applies full linting: syntax
checking (perl -wc), perltidy conformance, and perlcritic at its
default severity (5 - the most severe violations only). If your stub
or imported source isn't tidy, has a severity-5 perlcritic violation,
or fails to compile, the build - and therefore install - will fail.
Disable these gates with environment variables if you want to
bootstrap first and clean up after:
LINT=off make
SKIP_TESTS=1 make
LINT is interpreted by the build system installed by this
module; SKIP_TESTS is interpreted by CPAN::Maker to skip
running the test suite when building the distribution tarball.
Next Steps
-
Review the generated files
...particularly
buildspec.ymlwhich controls how the distribution is built as well asrequiresandtest-requireswhich list your module's dependencies. Your git identity is pre-populated from~/.gitconfigbut you may want to adjust the description or resource URLs.See CPAN::Maker for details regarding
buildspec.yml. -
Edit the generated stub in
lib/My/Module.pm.in.This is your primary source file - never edit the generated
.pmfile directly as it will be overwritten on the nextmake. -
Add More Modules and Scripts
As your project grows, add new modules to
lib/and scripts tobin/as.pm.inand.pl.infiles respectively. The build system discovers them automatically - no changes to the Makefile required. Add new test files tot/as.tfiles. -
Rebuild Your Distribution
When you are ready to build:
makeThis scans your source files for dependencies, regenerates
requiresandtest-requires, generatesREADME.mdfrom your POD, and produces a distributable tarball. -
Verify Your Tarball Installs Cleanly
To verify your distribution installs cleanly:
cpanm --local-lib=$HOME My-Module-*.tar.gz -
Put Your Project Under Source Control
To initialize version control and make your first commit:
make gitSet
NO_COMMIT=1if you don't want to commit yet.make git NO_COMMIT=1 -
Learn More About
CPAN::Maker::BootstrapperSee "EXTENDING THE BUILD SYSTEM" for customizing the build, dependency management details. See "FAQ" for common questions and recipes.
WHY YOU SHOULD CONSIDER USING CPAN::Maker::Bootstrapper
If you have ever reached for Jenkins, GitHub Actions, CircleCI, or a sprawling shell script to automate your Perl builds, consider what those tools actually require: a server or cloud account, a proprietary YAML DSL, plugin ecosystems with their own release cycles, containers, agents, and configuration files that only run in one specific environment.
The CPAN::Maker build system runs everywhere Perl runs - your
laptop, a remote EC2 instance, a colleague's workstation - with no
setup beyond cpanm CPAN::Maker::Bootstrapper. git clone && make
is always sufficient to build a fresh checkout.
The Stack
The build system is built on three tools that have been solving these problems correctly for decades:
- GNU make - dependency tracking, incremental builds, the target/prerequisite model is still the clearest expression of build this from that. A Makefile from 1990 still runs today.
- bash - process orchestration, file manipulation, conditionals, the Unix toolkit. Available on every system you will ever deploy to.
- Perl - text processing, CPAN ecosystem access, JSON, YAML, HTTP - anything complex enough to warrant a real language, right there in your build recipes without shelling out to another runtime.
Together they give you a complete, auditable, version-controlled build
system that is trivially debuggable with make -n and bash -x,
self-documents via make help, and needs no external services to run.
Best Practices Out of the Box
The installed build system encourages professional Perl development habits from the start:
- Source files are clearly separated - generated
.pmand.plfiles live alongside their.pm.inand.pl.insources. The build system always regenerates the.pmfrom the.pm.inon change, making it clear which file you own. Never edit the generated file directly - your changes will be overwritten on the nextmake. - Dependencies are tracked automatically -
scandeps-static.plscans your source files on every build, keepingrequiresandtest-requirescurrent. You stay in control via pinning, sticky entries, and skip lists. - Quality gates are built in -
perl -wcsyntax checking,perltidy, andperlcritic,podcheckerrun automatically on every build, stopping bad code before it enters the distribution. Gates can be selectively disabled via your configuration file or on the command line (make LINT=off) when you need a faster build during development. - The build system upgrades itself -
make updaterefreshes managed build files from the installed bootstrapper;make upgradechecks MetaCPAN and upgrades the bootstrapper itself. - Extension without modification -
project.mkis your upgrade-safe extension point. Add custom targets, inter-module dependencies, and project-specific variables there. The managedMakefileis never modified directly.
Perl Quality Tools
The build system supports optional Perl quality gates controlled via
your configuration file. Set the following keys in the [cpan-maker]
section:
syntax-checking = on # enables perl -wc on generated files
perltidyrc = ~/.perltidyrc # enables perltidy stage gate
perlcriticrc = ~/.perlcriticrc # enables perlcritic stage gate
These can be overridden per-run from the command line:
make SYNTAX_CHECKING=off # disable syntax checking
make PERLTIDYRC="" # disable tidy gate
make PERLCRITICRC="" # disable critic gate
Add modules that cannot be syntax-checked outside their runtime
environment to PERLWC_SKIP in project.mk:
PERLWC_SKIP = bin/startup.pl
Add inter-module build dependencies to project.mk when modules
depend on each other at build time:
lib/Foo/Bar.pm: lib/Foo.pm
To disable all linting at once:
make LINT=off
Or use make quick to disable both scanning and linting in one step.
A GNU Make Tutorial in Disguise
The .includes/ directory is also a practical demonstration of
advanced GNU make techniques that most developers never encounter -
working, production-tested examples you can learn from and adapt:
- Pattern rules and sentinel files for incremental quality gates
define/endefsnippets - reusable shell and Perl code blocks exported as make variables, eliminating duplication across recipes$(shell ...),$(eval ...),$(call ...),$(filter-out ...),$(addprefix ...),$(patsubst ...)- the full make function toolkit in real use?=,:=,+=, and=- all four assignment operators with their distinct evaluation semantics put to work- Order-only prerequisites,
.DEFAULT_GOAL,-include, and.SHELLFLAGS := -ec- advanced directives that tame complex builds - Trap-based temp file cleanup,
mktemp, and bash[[ ]]> conditionals inside make recipes - Perl snippets exported into make via
$(value ...)andexport- leveraging Perl's text processing power directly in the build
If GNU make is the cast-iron pan of build tools - virtually
indestructible, infinitely useful, and unfairly overlooked in favor of
shinier alternatives - then CPAN::Maker::Bootstrapper is the recipe
book that shows you what it can really do.
IMPORTING FILES
The --import|-I option allows you to bring existing Perl source
files into a new Bootstrapper project. This is the primary mechanism
for migrating an existing project or consuming a scaffold tarball
generated by cli-simple -scaffold.
The --import option may be specified multiple times to import from
several directories in a single operation:
cmb --module My::Script \
--import /path/to/roles \
--import /path/to/bin \
--installdir .
What Gets Imported
The importer recursively scans the path provided by --import and
brings in the following file types:
-
.pmfiles - copied tolib/as.pm.insource files, preserving the directory structure implied by the package name -
.plfiles - copied tobin/as.pl.insource files -
.tfiles - copied tot/ -
Executable files - copied to
bin/as.infiles.Note: Executable files are imported with their execute permission removed. The build system sets permissions appropriately when generating the final files from the
.insources.
All imported files receive the .in extension because they become
source inputs to the build system. The build generates the final
.pm, .pl, and script files from these sources, substituting
version tokens and running syntax checks along the way.
Module Name Requirement
When using --import you must also specify --module with the
primary module name of the distribution. The importer cannot infer
the module name from the imported files alone:
cmb --module My::Script --import /path/to/source --installdir .
The Build After Import
After creating the project source tree the importer runs make
with linting disabled but syntax checking and dependency scanning
enabled:
make LINT=off SYNTAX_CHECKING=on SCAN=on
This serves two purposes - it validates that the imported files are
syntactically correct Perl, and it runs scandeps-static.pl against
the source to seed the requires and test-requires dependency
files.
The build will attempt to produce a distribution tarball. If the
build fails, make.log and make.err are written to your current
working directory for diagnosis.
Next Steps After a Successful Import
After a successful build you have a complete, buildable CPAN distribution, although it may not reflect everything you need for your project. Typical next steps:
-
- Review and edit the generated
buildspec.yml- verify the module name, author, and resource links are correct
- Review and edit the generated
-
- Manually import files missed by the importer
Your project may want to package additional files that are installed into the distribution's share directory. Move them into an appropriate directory or the root of the project and add them to the
buildspec.ymlfile.extra_files: - ChangeLog <= included in distribution tarball, but not installed share: - config/some-file.ini <= installs config/some-file.in into the distribution's share directory - my-app.json <= installs my-app.json from the root of your project into the distribution's share directory -
- Initialize a git repository with
make git
- Initialize a git repository with
-
- Run
make tidyif you haveperltidyinstalled
- Run
-
- Run
maketo produce the final distribution tarball
By default the recipes in the
Makefilewill perform the following actions:-
Perform a syntax check (
perl -wc -I lib $@) on your source files -
Scan your source for dependencies
To turn this off:
make SCAN=off -
Run
perltidyon your source filesTo turn this off:
make PERLTIDYRC="" make LINT=off -
Run
perlcriticon your source filesmake PERLCRITICRC="" make LINT=off
To turn off everything except syntax checking:
make quick - Run
-
- Test installation:
cpanm -n -v ./My-Script-1.0.0.tar.gz
- Test installation:
Limitations
-
--importcannot be used with--stub- they are mutually exclusive ways to create the initial source -
The importer uses the package declarations inside
.pmfiles to determine where to place them underlib/. If the importer cannot match the filename with a package declaration inside the file, it will warn and skip that file -
Imported files are not tidied automatically. If you have
perltidyinstalled, runmake tidyafter import to bring the imported code into conformance with your.perltidyrcbefore committing -
If your imported modules have dependencies on each other, the syntax check phase of the build may fail because Make processes files independently and cannot guarantee build order. Add a
project.mkto declare inter-module dependencies:lib/My/Script.pm: \ lib/My/Script/Role/Frobnicate.pm \ lib/My/Script/Role/List.pmMake will then build your dependencies before attempting to syntax-check the main module. See "EXTENDING THE BUILD SYSTEM" for details on
project.mk.
Importing a CLI::Simple Scaffold Tarball
The import-scaffold command is a convenience wrapper around
--import specifically designed to consume tarballs generated by
cli-simple -scaffold:
cmb import-scaffold my-script-roles.tar.gz --module My::Script --installdir .
The tarball is extracted to a temporary directory and fed to the importer automatically. See CLI::Simple for details on generating scaffold tarballs.
CONFIGURATION
cmb can read your global .gitconfig file or
a properly formatted .ini file to populate some of the options used
when creating a distribution and using the AI commands. If you have a
GitHub user account add your username:
git config --global user.github <your-username>
If you typically create projects in one directory, add the basedir
option:
git config --global cpan-maker.basedir $HOME/git
If you want to create a different configuration file it should have at least the following entries:
[user]
email = your-email@somedomain
name = First Last
# use to construct GitHub resource URLs
github = github-user
[cpan-maker]
basedir = /home/myhome/git
# indicates the resources section of Makefile.PL should contain github references
resources = github
llm-api-key-helper = cat ~/.ssh/anthropic-api-key
-
llm-api-key-helperFor LLM commands (code-review, pod-review), you can specify a shell command that outputs your API key without exposing it in shell history:
llm-api-key-helper = cat ~/.ssh/anthropic-api-keyWhen set, this command is executed to retrieve the API key, avoiding the need to pass it on the command line or set it in the environment manually. This is the recommended secure approach.
See CPAN::Maker::ConfigReader for a complete description of the configuration file.
-
Use the
--configoption to use your custom config. -
Use
create-configto generate a starter configuration file:cmb create-config > ~/.cpan-makerrcThen point
cmbat it by setting theCPAN_MAKER_CONFIGenvironment variable in your shell profile:export CPAN_MAKER_CONFIG=$HOME/.cpan-makerrc
Environment
-
LLM_API_KEY
Your Anthropic Claude API key. Set this before running any LLM command (code-review, pod-review, release-notes).
The key is removed from environment so it is not inherited by child processes such as 'make'. This does not protect against memory inspection of the current process - see LLM::API for how the key is actuall stored using a closure to prevent accidental serialization via Dumper.
Avoid passing the key on the command line where it might be saved in history and can be seen in process lists.
-
CPAN_MAKER_CONFIG
Path to a configuration file (in .ini format) containing user settings such as name, email, GitHub username, and project base directory. If not set, the bootstrapper will attempt to read settings from ~/.gitconfig.
-
SCAN
Controls whether dependency scanning is performed during
make. Set to OFF or off to disable scanning. Default is ON.
INSTALLED PROJECT FILES
The following files are installed into the project directory:
-
Makefile- the complete build system. Derives all paths and names fromMODULE_NAMEor your stub file's package name. See "THE PROJECT MAKEFILE". -
buildspec.yml- generated from the template, pre-populated with your module name, git identity, GitHub username, and project URLs. -
lib/<Module/Path>.pm.in- stub module, populated from eitherclass-module.pm.tmpl(default) orcli-module.pm.tmpl(when--stub clioption is used). Contains package declaration,$VERSION, and a POD skeleton with your name and email from git config.Note: All source files in
lib/andbin/use the.pm.in/.pl.inconvention. These are the files you edit. The.pmand.plfiles are derived from them by the pattern rules in the Makefile, which substitute@PACKAGE_VERSION@with the current value ofVERSION. Never edit the generated.pmor.plfiles directly - your changes will be overwritten the next timemakeruns! -
t/00-<project-name>.t- minimal smoke test that callsuse_okon your module. -
.includes/- the managed build system directory. Contains all.mkfiles installed and maintained by the bootstrapper. These files are write-protected and should never be edited directly. Updated bymake update..includes/perl.mk - pattern rules, syntax checking, tidy, critic .includes/git.mk - make git target .includes/help.mk - make help target .includes/version.mk - make release/minor/major targets .includes/release-notes.mk - make release-notes target .includes/update.mk - make update target .includes/upgrade.mk - make upgrade/check-upgrade targets -
project.mk- your extension point for custom make rules, inter-module dependencies, and project-specific variables. Never touched bymake update. See "EXTENDING THE BUILD SYSTEM". -
modulino.tmpl- template used bymake modulinoto generate bash wrapper scripts for modulino-style modules. -
VERSION- contains the current version string inmajor.minor.patchformat. Managed bymake release,make minor, andmake major. -
ChangeLog- empty placeholder, required by the distribution. -
.prompts/
The first time you attempt to run
pod-revieworcode-reviewthe script will populate this directory with the default prompts.
THE PROJECT MAKEFILE
The installed Makefile is self-configuring. It can derive everything
from MODULE_NAME or the package name inside a custom stub file.
MODULE_PATH - lib/My/New/Module.pm (from MODULE_NAME)
PROJECT_NAME - My-New-Module (from MODULE_NAME)
TARBALL - My-New-Module-1.0.0.tar.gz (from PROJECT_NAME + VERSION)
If MODULE_NAME is not supplied on the command line, it is inferred
from the project directory name.
Key Makefile targets:
-
make/make allBuilds the distribution tarball. Generates
requires,test-requires, andREADME.mdas prerequisites. -
make requires/make test-requiresScans source files with
scandeps-static.pland writes the dependency files specified in thebuildspec.ymlfile used bymake-cpan-dist.pl.Note: By default, any change to your
.pm.infiles will trigger a rescan of your modules for new dependencies. This will add a significant delay when you have many modules and a large number of dependencies. You can avoid the scan by setting the environment variableSCANto any value other thanON(case insensitive).make SCAN=OFF -
make release/make minor/make majorBumps the patch, minor, or major version number in
VERSION. -
make release-notesGenerates a diff, file list, and tarball comparing the current version to the previous git tag.
-
make cleanRemoves generated files. Does not affect
buildspec.yml,VERSION, or any*.insource files.If your project needs to add a project specific clean recipe, use the
clean-localtarget with a double-colon.clean-local:: rm -rf workdir -
make tidyRuns
perltidyon all.pm.inand.pl.insource files using the profile specified byperltidyrcin your config. Requiresperltidyrcto be set. -
make criticRuns
perlcriticon all source files using the profile specified byperlcriticrcin your config. Requiresperlcriticrcto be set. -
make lintRuns both
make tidyandmake critic. -
make gitInitializes a git repository, stages all recommended project files including
.includes/*, and makes an initialBigBangcommit. -
make quickBuilds the distribution tarball with dependency scanning and all linting disabled. Useful during active development when you want fast iterative builds without waiting for
scandeps-static.plor quality gates.make quickEquivalent to:
make SCAN=off LINT=off -
make workflowInstalls a CI build script (
builder) and a GitHub Actions workflow (.github/workflows/build.yml) into your project, templated with your module and project name. Also merges any build-only dependenciesbuilderneeds intobuild-requires.make workflow git add build-requires builder .github/workflows/build.ymlCommit these files - GitHub Actions will then run
./builderon every push tomainordev. See "Continuous Integration" for whatbuilderdoes and how to run it outside of GitHub Actions. -
make build-ciRuns
builderlocally inside Docker, against your current branch, to reproduce a CI build without pushing. Requiresdockerand abuilderscript (runmake workflowfirst if you don't have one).make build-ciSee "Continuous Integration" for the variables that control this target.
README.md
The Makefile will automatically create a README.md from your
Perl module's pod. The stock buildspec.yml will include that
README.md in the distribution's share directory. If you want the
README.md to be included in the distribution but not installed,
edit the buildspec.yml file.
Before
extra-files:
- ChangeLog
- share:
- README.md
After extra-files: - ChangeLog - README.md
If you want a different README.md generated create a
README.md.in file. That file will be filtered through
md-utils.pl (from Markdown::Render) to produce a .md file.
COMMANDS
-
install (default)
Scaffolds a new project. This is the default command so:
cmb -m My::Module...is the same as:
cmb -m My::Module install -
create-config
Outputs a stub configuration file to STDOUT. Create and edit a new config to customize the behavior of
cmb.cmb create-config > ~/.cpan-makerrcThen set
CPAN_MAKER_CONFIGto point to it:export CPAN_MAKER_CONFIG=$HOME/.cpan-makerrc -
extra-files
cmb extra-files path file ...Add files to be installed with the distribution. Use '.' for path if the file is to be installed in the root of the distribution tarball but not in the share directory. Use 'share' if the file is to be installed int the distribution share directory.
NOTE: file should be the relative path within the project that points to the file.
Example:
cmb extra-files . README.md cmd extra-files share share/config.json
LLM Commands
The following commands require LLM::API to be installed and a valid Anthropic API key. Set it in the environment before running any LLM command:
export LLM_API_KEY=$(cat ~/.ssh/anthropic-api-key)
The key is deleted from the environment immediately after being read and
is never passed to child processes. See CPAN::Maker::ConfigReader for
the llm-api-key-helper option which avoids exposing the key in shell
history entirely.
SECURITY NOTE: Never pass your API key on the command line where it would be visible in shell history and process listings.
-
code-review
Submits a Perl module or script to the LLM for a code review. POD is automatically stripped before submission so token costs reflect code only. The review is written as a JSON file to the current directory.
cmb code-review [options] lib/My/Module.pmThe review file is named:
<module>-review-<timestamp>.codeA token usage summary is printed to stderr after the review completes.
If a review has been completed at least once the annotated review file is automatically sent with your code to re-focus the review. You must annotate the review file before resubmitting by running the
annotatecommand and marking each finding with a valid dispostion. See "THE REVIEW WORKFLOW" for details.Options specific to code-review:
--prompt|-p PATH path to a custom review prompt file --prompt-profile|-P NAME additive prompt profile (repeatable) --context|-C PATH context file to submit alongside the review (repeatable)Note: The prompt profile list and the context file list is written to the review output file. On subsequent runs these will be read from the review. You do not need to provide them unless you want to update their values.
-
annotate
Applies disposition tags to findings in the latest review file and displays the current annotation state. Must be run from a project directory (one containing
.includes/).cmb annotate [options] lib/My/Module.pmWithout options, displays the current annotation state of the latest review file. With
-aoptions, applies the specified dispositions before displaying.cmb annotate lib/My/Module.pm cmb annotate -a 1:wrong -a 2:reject lib/My/Module.pmOptions:
--annotate|-a N:DISPOSITION apply disposition to finding N (repeatable) --auto-annotate|-A annotate and immediately submit the next review --finalize-annotations|-F create versioned release artifactValid dispositions are
accept,reject,wrong,wrong-reconsider,defer, andconfirmed(case insensitive). See "THE REVIEW WORKFLOW" for a description of each. -
pod-finding
cmb pod-finding lib/CPAN/Maker/Bootstrapper.pmRun this after a
pod-reviewcommand to display a table of findings. -
pod-review
Submits a Perl module or script to the LLM for a documentation review. The full file including code is submitted so the LLM can check consistency between implementation and documentation. If no POD exists the LLM generates complete POD documentation ready to paste after
__END__.cmb pod-review lib/My/Module.pmThe review file is named:
<module>-review-<timestamp>.pod -
release-notes
Generates release notes for a given version using the LLM. Requires the release artifacts produced by
make release-notes:release-<version>.diffs release-<version>.lst release-<version>.tar.gz cmb release-notes <version>The generated release notes are written to
release-notes-<version>.md. Binary files are automatically excluded. Use--max-diff-filesto cap token consumption on large distributions (default: 50, 0 = unlimited). -
code-finding
Generates a table with the complete details of a finding.
cmb code-finding lib/My/Module.pm 1
OPTIONS
-
--annotate|-aN:DISPOSITIONSee "annotate"
-
--auto-annotate|-ASee "annotate"
-
--basedir|-bDIRBase directory in which to create the projects. Defaults to the current working directory when
--installdirand--basedirare not provided. The directory must exist or the script will throw an exception.Note: If
--installdiris provided it takes precedence and--basediris ignored.default: pwd
-
--color|--no-colordefault: color
To turn color off use --no-color.
-
--dry-run|-DDry run mode will abort after displaying a pre-submission token and cost estimation for the
pod-reviewandcode-reviewcommands. -
--config|-cconfiguration fileThe path to a
.inifile that contains configuration information used to scaffold your project.default: ~/.gitconfig
-
--color, --no-colorTurns coloring of the annotation summary table on or off.
default: on
-
--context|-CPATHOne or more files to submit with your code review file that provide additional context for the LLM during the review.
-
--email|-eEMAILOverride the author email. Defaults to
user.emailfrom your global git config. -
--finalize-annotations|-FSee "annotate"
-
--force|-fOverwrite an existing project. Without this flag, the command dies if a
Makefilealready exists in the target directory. -
--github-user|-gUSEROverride the GitHub username used to construct repository URLs in
buildspec.yml. Defaults touser.githubfrom your global git config. -
--import|-IpathA path that contains
.pmor.plfiles for importing into the project. You can specify multiple paths. You cannot use--stuband--importtogether.Example:
cmb --module Foo::Bar -I ~/foo-bar/lib -I ~/foo-bar/binWhen using the
--importoption, you must use the--moduleoption to specify the primary module name of the distribution. The importer cannot infer the module name from the imported files alone.Note: The
Makefilewill automatically attempt to substitute the token@PACKAGE_VERSION@inside your.pl.inor.pm.infiles with the current semantic version in theVERSIONfile. If you want to use that for versioning your scripts and modules add the token as shown below:our $VERSION = '@PACKAGE_VERSION@;' -
--installdir|-iDIRDirectory in which to create the project. Defaults to the current working directory. The directory is created if it does not exist.
Example:
cmb --installdir ~/git/My-ModuleThe install directory should include the project name.
Note:
--installdiroverrides--basedir. -
--max-diff-filesLIMITThe number of files inside the tarball that contains the changed files for release notes creation that can be uploaded to the LLM. Set to 0 for no limit.
default: 50
-
--max-tokens|-tTOKENSMaximum number of tokens the LLM may return in a single response. Higher values reduce the risk of truncated reviews on large files.
default: 4096 (set by LLM::API)
-
--model|-MMODELSpecifies the model id to use for the
pod-reviewandcode-reviewcommands.For
pod-reviewthe default model isclaude-haiku-4-5-20251001.For
code-reviewthe default mode isclaude-sonnet-4-6.The Haiku model tends to be better at summarizing documentation and avoiding unnecessary analysis around edge cases that contribute to noise.
Caution: Both models try hard to find issues to the point that you will almost never get a clean run when asking for a POD review. When your POD is complete, accurate and usable it's good enough. Avoid shaving the yak!
-
--module|-mMODULE (required)The Perl module name for the new project, e.g.
My::New::Module. Used to derive the project directory name, source file path, and tarball name. You can omit this option if you provide a stub file (--stub path) that contains a package name that is consistent with the stub's path. For example, if my package isMy::Appand the module path containsMy/Appthen the script will assume your module name isMy::App.cmb --stub $HOME/workdir/My/App.pm -
--prompt|-pPATHPath to a text file that will be used to prompt the LLM for a code or pod review.
defaults:
pod => .prompts/pod-review.prompt code => .prompts/code-review.prompt -
--prompt-profile|-PNAMEThe name of a prompt profile located in the
.promptsdirectory. One or more profile names may be specified. You need only provide the name (e.g. cli-tool). -
--resources|-rgithubCurrently takes only a single value: 'github' that indicates that the resources section of
Makefile.PLshould be populated with GitHub URL references. Future versions may support additional providers. -
--stub|-sTYPE|PATHControls the module stub used to generate the initial
.pm.insource file. Three forms are accepted:- Omitted - uses the default plain class stub (
class-module.pm.tmpl). cli- uses the CLI stub (cli-module.pm.tmpl), which inherits from CLI::Simple and includes a skeletonmain,init, and a placeholder command.- A file path - uses the specified file as the stub. The file
must exist or the command will die with an error. This allows you to
supply your own template or bootstrap a project around a module you
have already started writing. You can omit the
--moduleoption if you supply your own stub file. See the explanation for the--moduleoption for details.
When specifying a stub you cannot use the
--importoption. - Omitted - uses the default plain class stub (
-
--username|-uNAMEOverride the author name used in the module stub and
buildspec.yml. Defaults touser.namefrom your global git config.
THE REVIEW WORKFLOW
CPAN::Maker::Bootstrapper allow you implement a structured
iterative code review workflow built around JSON review files and
developer-applied disposition annotations. The workflow converges over
several rounds, with each round potentially costing less as noise is
suppressed and findings are resolved.
Overview
Each review round consists of three steps:
-
-
Run a review
cmb code-review --prompt-profile cli-tool lib/My/Module.pm
The review is written to a timestamped
.codefile containing a JSON object withfindings,confirmations, anddeferredarrays. -
-
- Annotate the findings
An annotation is how you mark a finding with a disposition. The dispositions are used by the LLM during thenext review. See "Dispositions".
cmb annotate lib/My/Module.pmThis displays the current annotation state. Apply dispositions with
-aoptions:cmb annotate -a 1:accept -a 2:wrong -a 3:reject -a 4:defer lib/My/Module.pmYou can annotate incrementally across multiple invocations. Each call shows the updated state so you always know what remains.
-
- Submit the next review
Once all findings are annotated and code updated if necessary, run the next review. The bootstrapper automatically finds and submits the latest annotated review file with your updated code:
cmb code-review lib/My/Module.pmAlternatively, use
--auto-annotate|-Awith theannotatecommand to annotate and immediately resubmit in one step:cmb annotate -a 1:wrong -a 2:reject --auto-annotate \ lib/My/Module.pmThe LLM will honor all dispositions from the prior round, confirm fixes marked
ACCEPT, carry forwardDEFERitems, and suppressREJECTandWRONGfindings. New findings appear without noise from settled questions.
Dry Run Mode
Before your prompt and code are submitted for review, the script will output a table of showing you the estimated cosst based on token counts. The input token count is derived by calling the "COUNT TOKEN" endpoint API with the message to be submitted for review. The input token count is therefore accurate, while the output token count is an estimate.
To stop the script for actually submitting the message for review, use
the --dry-run option. This will abort the process immediately prior
to submission.
Dispositions
Each finding in the annotations file must be given one of the dispositions describe below before the next review can be submitted. The prompt sent to the LLM is designed around these dispositions. This helps the LLM produce findings that hopefully will converge on a clean review.
-
ACCEPT
The finding is valid and has been fixed. On the next review the LLM will confirm the fix is present. If the fix is not found the finding will be re-raised.
-
REJECT
The finding has been reviewed and dismissed as inapplicable to this codebase or context. It will not be raised again in subsequent reviews.
-
WRONG
The finding was based on faulty reasoning. The code is correct. The finding will not be re-raised. Use this when the LLM has misread the control flow, misunderstood the design intent, or applied an inappropriate threat model.
-
WRONG-RECONSIDER
Applied automatically at finalization to all findings marked WRONG. On the first review of the next version the LLM will re-examine the specific function and code excerpt carefully. If the prior analysis was still incorrect the finding reverts to WRONG. If the code has changed and the finding is now valid it is raised as a new finding. If the model understands specifically why its prior reasoning was wrong it may mark the finding CONFIRMED.
-
DEFER
The finding is known and acknowledged but not yet addressed. It is carried forward in the
deferredarray of each subsequent review without being treated as a new finding. -
CONFIRMED
Used for logic confirmations rather than defects. Marks that both the LLM and the developer agree the code is correct.
Diminishing Returns and When to Stop
Run the annotate command after each review submission to view the
findings. Each round tends to surface smaller and more obscure issues
as obvious findings are resolved. Despite some fairly aggressive
attempts to create prompts that prevent trivial or obscure findings
you should stop when you see these signals:
- All new findings are LOW severity.
- The LLM is re-raising findings already marked WRONG or REJECT, possibly rephrased (LLMs can and do make mistakes!).
- New findings describe edge cases that cannot occur in normal usage.
When all findings have dispositions and no new substantive issues appear, the code is ready to ship.
The Release Artifact
When you are satisfied with the review state, finalize it with
--finalize-annotations:
cmb annotate --finalize-annotations -a 1:wrong -a 2:reject lib/My/Module.pm
This applies any remaining dispositions, validates that all findings
are annotated, reads the version from the VERSION file, and writes
the versioned release artifact:
CPAN-Maker-Bootstrapper-1.1.0-REVIEW.json
This file serves as a code review certification for the release - a machine-readable record of every finding examined, every logic confirmation made, and every disposition applied before the version was published. Commit it to the repository alongside your ChangeLog.
All findings marked WRONG are automatically converted to WRONG-RECONSIDER in the release artifact, prompting careful re-examination on the first review of the next version rather than permanent suppression.
Cost Management
Typical review costs run $0.05-0.10 per run on a moderately sized
module with POD stripped depending on the model you choose. The
default model used for POD review is claude-haiku-4-5-20251001 and
claude-sonnet-4-6 for code review. Costs decrease over successive
rounds as the model spends fewer output tokens re-explaining
suppressed findings.
Use your own prompt profiles (--prompt-profile) to suppress entire
classes of noise before they reach the annotation file. A well-tuned
profile for your application type is the highest-leverage cost
reduction available.
See Also
"LLM Commands", "PROMPT PROFILES", CPAN::Maker::ConfigReader
PROMPT PROFILES
Prompt profiles are additive prompt fragments that customize the review behavior for specific application types. They are appended to the base review prompt before submission and are intended to focus the review on relevant concerns while suppressing noise that does not apply to the target context.
NOTE: Prompts count toward your input token count. Be succinct and accurate.
Using Profiles
Pass one or more profiles using the --prompt-profile option:
cmb code-review --prompt-profile cli-tool MyModule.pm
Multiple profiles may be combined:
cmb code-review --prompt-profile cli-tool --prompt-profile security MyModule.pm
Profiles are resolved from the .prompts/ directory in the current
project. A profile named cli-tool resolves to
.prompts/cli-tool.prompt. Add your own prompt profiles and commit
them to your project.
Built-in Profiles
The following profile is installed with the distribution:
-
cli-tool
Appropriate for single-user developer CLI tools. Suppresses security findings that assume a multi-user or hostile environment, TOCTOU race condition findings that assume concurrent invocation, and concerns about
qx{}orsystem()calls where input originates from the user's own configuration. Also assumesperlcriticandperltidyare enforced in the development environment.
Creating Custom Profiles
A profile is a plain text file in .prompts/ containing additional
prompt instructions, one per line. Lines beginning with # are treated
as comments and stripped before submission. Profile instructions use the
same format as the base review prompt.
Example .prompts/security.prompt:
# security profile - add to any review where input handling matters
- Treat all caller-supplied input as untrusted regardless of source.
- Flag any use of eval, system, or exec that incorporates external data.
- Flag missing taint checks on data used in file or system operations.
Planned Profiles
The following profiles are planned for future releases:
-
library
Focuses on API contract correctness and caller assumptions. Appropriate for CPAN distributions intended for use by unknown callers.
-
web-application
Treats external input as untrusted. Flags injection risks, authentication gaps, and session handling concerns.
-
mod-perl-handler
Addresses Apache lifecycle concerns including global state, startup versus request time initialization, and child process behavior.
-
lambda-function
Focuses on cold start performance, statelessness, and environment variable handling appropriate for AWS Lambda deployments.
Community contributions of additional profiles are welcome. See https://github.com/rlauer6/CPAN-Maker-Bootstrapper/issues.
EXTENDING THE BUILD SYSTEM
The bootstrapper's Makefile is intended to be immutable and work
across all of the projects that use CPAN::Maker::Bootstrapper. Our
goal is to keep Makefile working for you even when we make updates
to the bootstrapper.
However, you own Makefile and are free to do with it as you
please. But we strongly advise that you read the sections below and
follow the recipe as the saying goes, to use and update the build
system as it was intended.
The installed Makefile is a managed file - it can be updated by
using the make target update when a new version of
CPAN::Maker::Bootstrapper is released.
make update
You are strongly advised not to modify the Makefile - your changes
will be overwritten if you run make update.
Instead, the recommended workflow, should you need to add new make
targets or control the order of the build based on dependencies is to
add those to project.mk. All managed build system files live in
the .includes/ directory where they are write-protected and clearly
separated from your project files. The Makefile includes them
automatically and conditionally includes project.mk from the
project root:
include .includes/perl.mk
include .includes/help.mk
include .includes/version.mk
include .includes/release-notes.mk
include .includes/git.mk
include .includes/update.mk
include .includes/upgrade.mk
-include project.mk
project.mk remains in the project root - it is your file, always
writable, and never touched by make update. The leading - on
its include means make will not complain if it does not exist yet.
This gives you a sanctioned, upgrade-safe extension point for
anything project-specific.
How the Makefile Works
The installed Makefile is structured around a few key concepts:
- Source files live in
lib/as.pm.inand inbin/as.pl.in. The build generates the final.pmand.plfiles from these sources by substituting@PACKAGE_VERSIONE@and other tokens, running syntax checks, and optionally running perltidy and perlcritic. - Sentinel files -
.tdyand.critfiles track whether a source file has passed tidiness and critic checks. These are regenerated only when the source changes. - Dependency scanning -
scandeps-static.plscans your source files and generatesrequiresandtest-requiresfiles which feed intoMakefile.PL. Controlled bySCAN=on|off. - The distribution tarball is the final output of
make. It is built bymake-cpan-dist.plusingbuildspec.yml.
Key variables you can override on the make command line or in
project.mk:
SCAN=off- skip dependency scanningLINT=off- skip perltidy and perlcriticSYNTAX_CHECKING=off- skipperl -wcsyntax checksMIN_PERL_VERSION=5.016- minimum Perl version for Makefile.PLPERLTIDYRC=/path/to/rc- path to perltidy configurationPERLCRITICRC=/path/to/rc- path to perlcritic configurationSKIP_TESTS=1- interpreted by CPAN::Maker; skips running the test suite when building the distribution tarballPERLWC_SKIP="file1 file2"- space-separated list of files to exclude from syntax and POD checksPOD=extract|remove- extract POD to a companion.podfile or strip it entirely from the built.pmPERLINCLUDE="-I path"- additional include paths used during theperl -wcsyntax check
What Belongs in project.mk
-
Custom targets
Any target specific to your project - generating assets, running linters, deploying, sending notifications:
.PHONY: deploy deploy: all scp $(TARBALL) user@myserver:/opt/cpan -
Inter-module dependencies
If your modules have build-time dependencies on each other, declare them here rather than modifying the Makefile:
lib/Foo/Bar.pm: lib/Foo.pm -
Additional file generation
If your project generates code or configuration from templates beyond what the standard Makefile handles:
lib/Foo/Generated.pm.in: schema/foo.json perl bin/generate-module.pl $< > $@ -
Project-specific variables
DEPLOY_HOST = myserver.example.com DEPLOY_PATH = /opt/cpan/incoming -
Extending CLEANFILES
Add project-specific generated files to the cleanup target by appending to
CLEANFILES:CLEANFILES += mygenerated.pm config/generated.yml -
Extending the
clean-recipeclean-local:: rm -rf workd
What does NOT belong in project.mk
- Modifications to existing targets like
all,clean,requires - Changes to
DEPS,CLEANFILES, or other core variables - these are owned by the managed Makefile - Anything that duplicates logic already in the managed Makefile
Keeping the build system up to date
The following targets manage the lifecycle of the build system itself:
-
make check-upgrade/make upgrade-checkChecks MetaCPAN to see if a newer version of
CPAN::Maker::Bootstrapperis available. -
make upgradeChecks MetaCPAN, installs the latest version via
cpanm, then automatically runsmake updateto refresh the managed project files. -
make updateCopies the managed files from the currently installed bootstrapper distribution into your project directory. After running, use
git diffto review what changed andgit checkout <file>to revert any changes you don't want.The following files are managed and may be updated:
Makefile .includes/git.mk .includes/help.mk .includes/update.mk .includes/upgrade.mk .includes/version.mk .includes/perl.mk .includes/release-notes.mk modulino.tmplYour
project.mk,buildspec.yml,requires,VERSION, source files and tests are never touched bymake update. -
make cpanmInstalls
cpanminusif it is not already available on yourPATH. Required formake upgradeto work:make cpanm && make upgrade
Automatic Drift and Update Checks
Every build runs two checks before proceeding, so you don't have to
remember to run make check-upgrade yourself:
- Is a newer
CPAN::Maker::Bootstrapperpublished on CPAN than the one installed on this machine? - Do this project's managed files still match what the currently
installed
CPAN::Maker::Bootstrapperwould produce?
These are independent questions - your installed bootstrapper can be
fully current while a given project has still drifted from it (most
commonly because the project hasn't been through make update
since you last upgraded), or your bootstrapper itself can be behind
CPAN while every project stays perfectly in sync with it.
Drift can happen for either of two reasons: your installed
CPAN::Maker::Bootstrapper was upgraded since this project last ran
make update, or a managed file was hand-edited despite the
warnings not to (see "What You Should Never Modify"). make
doesn't try to tell these apart - the fix is the same either way:
make update
Two variables, set in config.mk, control how strict these checks
are:
-
CMB_UPDATE_CHECK(on|off, defaulton)Set to
offto skip the MetaCPAN lookup - useful in CI or offline environments where the network call would just fail or slow things down. -
CMB_VERSION_DRIFT(fail|warn|ignore, defaultfail)Controls what happens when a project's managed files no longer match the installed bootstrapper.
failstops the build until you runmake update;warnprints a message and continues;ignoreskips the check entirely.
What You Should Never Modify
The files in .includes/ - perl.mk, git.mk, help.mk etc.
- are managed files that will be overwritten by make update. Do
not modify them directly. If you need to override behavior they
provide, do so in project.mk using Make's double-colon rule
pattern or by setting variables before the include.
The Makefile itself is also managed and will be overwritten by
make update. Your extension point is exclusively project.mk.
Dependencies Management
The Makefile will attempt to detect Perl module dependencies by
scanning .pm.in and .pl.in files and creating the requires and
test-requires files whenever you run make. These files are used
by the make-cpan-dist.pl utility to specify the dependencies in your
CPAN distribution file. You can prevent that by setting the environment
variable SCAN=OFF. The default is SCAN=ON.
To prevent an entry from being removed by a rescan, prefix the module
name with +. These entries are sticky and survive all subsequent
scans even if the scanner no longer detects them. To pin a specific
version, simply edit the version number in the requires file. If
the scanner subsequently detects a different version, the Makefile
will preserve your pinned version. Note that pinned versions are
never updated automatically - if you want to adopt a newer version
you must edit the file manually.
In your requires file:
+Foo::Bar 1.0 # sticky - survives all rescans
Baz::Qux 2.5 # version pinned - scanner won't override this version
Note: These two mechanisms are independent - + controls whether an entry
survives rescans, while the version number controls what version is
required.
MODULINOS
A modulino is a Perl module that doubles as a runnable script by checking whether it was invoked directly or loaded as a library:
package Foo::Bar;
caller or __PACKAGE__->main;
sub main {
...
exit 0;
}
Modulinos are useful for CLI scripts because they encourage encapsulation, simplify unit testing, and keep logic organized in named methods rather than inline code.
The Makefile provides a modulino target that generates a bash
wrapper script that invokes your module. By default it uses
MODULE_NAME, producing a script named after the module:
make modulino
For a project named Foo::Bar this creates bin/foo-bar.in.
make then builds bin/foo-bar from that source file via a
pattern rule, and the executable ends up in the distribution.
To create a modulino wrapper for a module other than the primary
project module, override MODULE_NAME:
make modulino MODULE_NAME=Foo::Bar::Buz
This creates bin/foo-bar-buz.in invoking Foo::Bar::Buz.
To give the wrapper a short or memorable name independent of the
module name, set ALIAS:
make modulino MODULE_NAME=Foo::Bar::Buz ALIAS=fbb
This creates bin/fbb.in which still invokes Foo::Bar::Buz.
ALIAS accepts either a plain name (fbb) or a module-style
name (Foo::Bar::Buz) - colons are converted to hyphens and
the result is lowercased.
The generated wrapper scripts (without the .in suffix) are
automatically added to .gitignore since they are build artifacts.
The .in source files are tracked by git.
Continuous Integration
make workflow installs builder, a self-contained bash script
that performs a clean-room build: it installs a minimal Perl
toolchain, installs your distribution's dependencies, and runs
make. It's designed to run unmodified in GitHub Actions, in any
other CI runner, or by hand from the command line.
Running builder manually
builder can build any git repository, not just the one it's
installed in - useful for testing a build in isolation without
touching your working tree:
docker run --rm -v "$(pwd)/builder:/builder:ro" \
-e BUILD_BRANCH=$(git branch --show-current) \
debian:trixie \
bash /builder https://github.com/your-user/Your-Module.git
Note: a repository URL is currently required - builder does not
yet support building an already-checked-out project mounted directly
into the container.
Environment variables
-
INSTALLERWhich tool
builderuses to install dependencies. Acceptscpmorcpanm-based values.default:
cpm install -g --show-build-log-on-failure --verbose -
PERLTIDYRC/PERLCRITICRCbuildersearches the checked-out repository for.perltidyrcorperltidyrc(respectively.perlcriticrc/perlcriticrc) and exports them automatically. When found,builderalso installsPerl::TidyorPerl::Critic(plus the community/compatibility policy modules) as extra build dependencies before runningmake. -
BUILD_BRANCH/GITHUB_REF_NAMEThe branch to check out and build.
GITHUB_REF_NAMEis set automatically by GitHub Actions;BUILD_BRANCHis the equivalent override for manual or non-GitHub runs. Falls back to the current branch if neither is set.
Override files
Commit these to your project root to customize a CI build without
touching builder itself:
-
build-apt-depsWhitespace-separated list of additional Debian packages to install before the build (
builderalways installs a minimal base:git gcc make perl curl ca-certificates libexpat-dev libssl-dev libzip-dev). -
build-mirrorsOne CPAN mirror URL per line - for example, your DarkPAN. Merged with
https://cpan.metacpan.orgwhen resolving dependencies.
make build-ci variables
make build-ci wraps the manual docker invocation above. It accepts:
DOCKER_BUILD_IMAGE - container image to build in (default: debian:trixie)
BRANCH - branch to build (default: current branch)
INSTALLER - see L</Environment variables> above (default: cpm)
BUILD_LOG - path to write build output (default: timestamped)
See Also
"make workflow", "make build-ci"
PREREQUISITES
The following tool(s) must be on your PATH:
git- used to read global identity configmake- GNU make is required to build the projectcurl- used bymake upgradeto query MetaCPAN
CAVEATS
-
.pmand.plGenerationThese files are generated from
.pm.inand.pl.infiles in the Makefile by filtering them through asedcommand that replaces certain tokens like@PACKAGE_VERSION@with values. The generated files are read-only. Always edit the.infile version.Use
@PACKAGE_VERSION@like this:our $VERSION ='``@PACKAGE_VERSION@``'; -
The import feature cannot be used with
--stub -
git
There is an assumption that users of this script are also
gitusers.gitis required to runmake gitwhich instatiates a git project and makes an intial commit. It's also used to look into your.gitconfigfile for your name and email address to populate the certain element in the resources file used when building your CPAN distribution.
FAQ
My build is failing with a module not found error during syntax
checking
This is almost always a build-time dependency ordering issue. If
lib/Foo/Bar.pm uses lib/Foo.pm, make may attempt to build and
syntax-check Foo/Bar.pm before Foo.pm exists. Declare the
dependency in project.mk:
lib/Foo/Bar.pm: lib/Foo.pm
This tells make to build Foo.pm first. See "Inter-module
dependencies" for details.
If the module genuinely cannot be loaded outside its runtime
environment (an Apache handler, a mod_perl module, etc.), add it to
PERLWC_SKIP in project.mk:
PERLWC_SKIP = lib/My/Apache/Handler.pm
How do I do a fast build during development?
make quick
This disables dependency scanning and all linting (syntax checking,
perltidy, perlcritic) for the current build. Your requires and
test-requires files are not updated and no quality gates run.
Use make without flags when you are ready to do a full build before
committing or releasing.
You can also disable individual features:
make SCAN=off # skip dependency scanning only
make LINT=off # skip all linting only
make SYNTAX_CHECKING=off # skip syntax checking only
How do I add a new module or script to the project?
Create the source file with the .pm.in or .pl.in extension in
the appropriate directory:
lib/My/New/Module.pm.in
bin/my-script.pl.in
The build system discovers them automatically via find-files - no
changes to the Makefile are required. The next make will include
them in the dependency scan and the distribution.
How do I include additional files in the distribution?
Edit buildspec.yml and add entries to the extra-files section:
extra-files:
- ChangeLog
- README.md
- share:
- my-config-template.yml
- my-data-file.json
Files listed under share: are installed into the distribution's
share directory and can be accessed at runtime via
File::ShareDir.
I want to pin a version or add a module the scanner missed
Edit requires directly. Prefix the module name with + to make
the entry sticky - it will survive all subsequent rescans even if the
scanner no longer detects it:
+My::Required::Module 1.5
To pin a version without making the entry sticky, just set the version number. The scanner will preserve your version if it detects a different one on subsequent builds:
Some::Module 2.0
These two mechanisms are independent - + controls survivability,
the version number controls what version is required. See "Dependencies"
for full details.
I want to exclude a module the scanner found
Create a requires.skip file in the project root with one module
name per line:
My::Own::Module
Some::Transitive::Dep
The scanner will never add these to requires. Use
test-requires.skip for the same effect on test dependencies.
Note that on a clean first build neither skip file has any effect
since there is no prior requires file to compare against. The skip
list takes effect from the second build onward.
I edited a .pm file and my changes disappeared
The .pm files in lib/ are generated from the .pm.in sources
and are write-protected. Always edit the .pm.in file - the .pm
is regenerated on every make and your changes will be lost.
If you are unsure which file to edit:
ls -l lib/My/Module.pm lib/My/Module.pm.in
The .pm.in file is the one you own.
Why does my build say it has drifted from the installed bootstrapper?
This means your project's managed files (Makefile,
.includes/*.mk) no longer match what your currently installed
CPAN::Maker::Bootstrapper would generate. There are two ways this
happens - upgrading your bootstrapper (e.g. via cpanm \--upgrade-all) instantly "drifts" every project you haven't yet
updated, or a managed file was edited by hand. Both are fixed the
same way:
make update
If you don't want a drifted project to fail the build outright, set
CMB_VERSION_DRIFT=warn (or =ignore) in that project's
config.mk. See "Automatic Drift and Update Checks".
=head2 make update overwrote something I changed in a managed file
The managed files in .includes/ should never be edited directly -
that is what project.mk is for. However if you did modify a managed
file and make update overwrote it, git has you covered:
git diff .includes/perl.mk
git checkout .includes/perl.mk
This is why make git and committing your .includes/ directory is
strongly recommended - git is your safety net for the entire build
system.
make says nothing to do but my source changed
The most common cause is that the generated .pm file is newer than
the .pm.in source. This can happen if you accidentally edited the
.pm directly or if file timestamps got out of sync. Force a rebuild:
touch lib/My/Module.pm.in
Or do a clean rebuild:
make clean && make
How do I disable scanning temporarily?
make SCAN=off
This skips the dependency scan entirely for that run - useful when
you have many modules and want a fast build during active development.
The default is SCAN=ON.
How do I disable syntax checking temporarily?
make SYNTAX_CHECKING=off
Similarly you can disable individual quality gates:
make PERLTIDYRC="" PERLCRITICRC=""
How do I upgrade the build system?
make upgrade
This checks MetaCPAN for a newer version of
CPAN::Maker::Bootstrapper, installs it via cpanm, and
automatically refreshes the managed files in .includes/ with
make update. Review the changes with git diff and revert
anything you don't want with git checkout.
If cpanm is not installed:
make cpanm && make upgrade
I want to add a bash script to my distribution
Create the script in bin/ with a .sh.in extension:
bin/my-script.sh.in
The build system will process it through the standard token
substitution (replacing @PACKAGE_VERSION@ and
@MODULE_NAME@), make it executable, and include it in the
distribution automatically.
If your script is more than a few lines of bash, consider writing it as a modulino instead - a Perl module that doubles as a runnable script. Modulinos are easier to test, encourage encapsulation, and give you the full power of Perl and CPAN. The build system has first-class support for them:
make modulino
This generates a bash wrapper in bin/ that invokes your module as
a script if it uses the modulino pattern:
caller or __PACKAGE__->main;
See "MODULINOS" for full details.
What is make release-notes used for?
make release-notes generates three artifacts comparing the current
working state of your repository against the previous git tag:
release-<version>.diffs- a unified diff of all changed filesrelease-<version>.lst- a list of added, modified, and removed filesrelease-<version>.tar.gz- a tarball containing only the changed files
These are primarily useful for generating release notes and changelogs,
and for submitting targeted patches. Run it after bumping the version
with make release, make minor, or make major and before
publishing to CPAN:
make minor
make release-notes
# review release-1.1.0.diffs
make
The artifacts are all the clues needed for LLMs to produce accurate and well written release notes for your project.
The release artifacts are cleaned up by make clean.
Can I distribute the POD in my modules separately?
When you package your CPAN distribution you can strip the pod from
your modules or you can extract the pod and provide them as separate
.pod files. There are two make environment variables you can set
to control that behavior.
-
make POD=extractextractwill strip POD from your module and create a.podfile containing the stripped POD that will be added to your distribution. -
make POD=removeremovewill strip POD from your module. No POD will be included in the distribution.
The dependency resolver keeps adding a file I don't want to
list. How can I tell it to skip those files?
Add a requires.skip file to exclude modules from the scanned
list. Sometimes the scanner may include modules that are optional or
modules you just don't want to include as requirements because they
are already included in a module you have already required.
Similarly, test-requires.skip excludes modules from the test
dependency scan.
On a clean first run neither requires nor test-requires exists
yet, so the raw scanner output becomes the dependency file - meaning
skip list and pins have no effect until the second run.
Something still doesn't work - how do I report an issue?
First check the "FAQ" sections above - your issue may already be covered.
If you believe you have found a bug or want to request a feature, please open an issue on GitHub:
https://github.com/rlauer6/CPAN-Maker-Bootstrapper/issues
When reporting a bug please include:
- The version of
CPAN::Maker::Bootstrapper(cmb --versionorperl -MCPAN::Maker::Bootstrapper -e 'print $CPAN::Maker::Bootstrapper::VERSION') - The output of
make -normake --debug=vif the issue is build-related - Your
buildspec.ymlandproject.mkif relevant (redact any sensitive information) - The Perl and GNU make versions (
perl --version,make --version) - MAKE SURE YOUR SUBMISSION DOES NOT CONTAIN SECRETS!
Pull requests are welcome. The project follows the standard GitHub fork-and-PR workflow.
SEE ALSO
CPAN::Maker - the distribution builder driven by buildspec.yml
(includes make-cpan-dist.pl)
CLI::Simple - the CLI framework used by the bootstrapper itself and optionally by generated CLI module stubs
CPAN::Maker::ConfigReader - the git config reader bundled with this distribution, available for use in your own tools.
LLM::API - client interface to Anthropic's Claude API
Module::ScanDeps::Static - the static dependency scanner used by
make requires and make test-requires to analyze your source files
DEPENDENCIES
CLI::Simple::Constants
CLI::Simple::Utils
CPAN::Maker::ConfigReader
Cwd
English
Email::Valid
File::Basename
File::Copy
File::Find
File::Path
File::ShareDir
File::Temp
JSON::PP
List::Util
Module::Metadata;
Required for AI Commands
Archive::Tar
Pod::Extract (required for code-review command)
Text::ASCIITable;
Recommend Packages
Term::ANSIColor
VERSION
This documentation refers to version 2.0.9
AUTHOR
Rob Lauer - rlauer@treasurersbriefcase.com
LICENSE
Copyright 2026, Robert C. Lauer All right reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.