NAME
CPAN::Maker::Bootstrapper - A complete build, dependency, and release framework for CPAN distributions
SYNOPSIS
# Bootstrap an existing project
cd /path/to/Foo-Bar
cmb --import .
# 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
mkdir My-Module
cd My-Module
cmb -I /path/to/my-module/lib -I /path/to/my-module/bin \
--installdir .
# 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)
cmb code-review lib/My/Module.pm
DESCRIPTION
CPAN::Maker::Bootstrapper provides a complete development framework for building, testing, maintaining, and releasing CPAN distributions.
It can scaffold a new Perl project or import an existing one, installing a managed build framework that carries the project from source through testing, dependency management, distribution, and release.
Core features include:
Continuous dependency discovery and maintenance for runtime, test, recommended, and suggested dependencies
Syntax checking, perltidy, perlcritic, POD checking, and other build-time quality gates
Hermetic project-local dependency installation coupled with syntax checking of modules and scripts to expose undeclared dependencies
Extensible project-specific build logic through project.mk without modifying managed build files
Semantic versioning, release-note generation, CPAN publishing, and CI workflow support
DarkPAN dependency manifests for distributions that depend on modules published outside CPAN
Build-system update checks and managed-file drift detection, with support for upgrading and refreshing the framework
AI-assisted code review, POD review and generation, structured finding annotation, and release-note generation
See "LLM Commands" and "THE REVIEW WORKFLOW" for details on the AI-assisted development tools.
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.
Scaffolding a new project from the default stub
cmb --module My::Module
or
# create a directory for the new CPAN distribution
mkdir My-Module
# cd into the directory
cd My-Module
# scaffold the project
cmb --installdir .
The bootstrapper derives the primary module name from the directory name (My::Module in this example). It then installs the build system, generates the stub source and test files, and runs make automatically to create the first distribution tarball.
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).
Bootstrapping an existing project
If you already have a Perl project, run cmb from the project root and import the current directory:
cd Foo-Bar
cmb --import .
When exactly one import directory is supplied and neither --module nor --installdir determines the module name, the bootstrapper derives the primary module name from the import directory. In this example, Foo-Bar implies Foo::Bar.
Use --dry-run first if you want to inspect the import plan without creating files or running the generated build:
cmb --import . --dry-run
You may also import from multiple directories and specify the primary module explicitly:
cmb --module Foo::Bar
--import lib
--import bin
--installdir /tmp/Foo-Bar
NEXT STEPS
- Review the project source
-
For a newly scaffolded project, edit the generated source file:
lib/My/Module.pm.inFor an imported project, review the
.pm.inand.pl.infiles created from the imported modules and scripts.Files ending in
.inare the editable project sources. The generated.pmand.plfiles are build artifacts and will be overwritten by subsequentmakeinvocations. - Review the framework's populated artifacts
-
- buildspec.yml - controls how the distribution is built
-
See CPAN::Maker for details regarding buildspec.yml.
- requires - found dependencies
- test-requires - test dependencies
- recommends - recommended dependencies
- suggests - optional dependencies
- Manage the project version
-
The bootstrapper creates a VERSION file containing the semantic version number for the project. New projects begin at
1.0.0.Use the version targets to increment it:
make release # 1.0.0 -> 1.0.1 make minor # 1.0.1 -> 1.1.0 make major # 1.1.0 -> 2.0.0If you imported the primary module rather than generating it from a stub, make sure its version declaration uses the project version token:
our $VERSION = C<E<64>PACKAGE_VERSIONE<64>>;When
makegenerates the .pm file from its .pm.in source,@PACKAGE_VERSION@is replaced with the value from VERSION. - Add More Modules, Scripts, and Tests
-
As your project grows, add new modules beneath lib/ as .pm.in files and scripts beneath bin/ as .pl.in files. The build system discovers them automatically - no Makefile changes are required.
Distribution tests belong beneath t/.
Additional test suites may use the conventional extended-test directories:
xt/author/ xt/release/ xt/smoke/These suites can be run explicitly with
make test-author,make test-release, andmake test-smoke, or together withmake test-all. - Add additional files to the distribution
-
Edit buildspec.yml to declare additional files that should be included in the distribution.
Files may be added at the distribution root or installed beneath the distribution's share directory.
For example:
extra-files: - ChangeLog - share: - config/example.ini - defaults.jsonUse root-level entries for files that should be packaged but not installed into the share directory.
Use the
sharesection for files that should be installed as distribution data.See "How do I include additional files in the distribution?" for more details.
- Rebuild Your Distribution
-
When you are ready to rebuild the project:
makeThe default build applies the project's dependency and quality gates, regenerates derived source files and documentation as needed, and produces the CPAN distribution tarball.
When dependency scanning is enabled, changed source files are scanned and the dependency artifacts are updated only when their contents have changed:
requires test-requires recommends suggestsThe build also regenerates README.md when required and rebuilds only those derived artifacts whose prerequisites have changed.
For a faster development build that skips dependency scanning and both lint tools while retaining syntax checking, use:
make quick - Verify the Distribution Installs Cleanly
-
After building the distribution, install the generated tarball with
cpanmto verify that it can be consumed independently of the source tree:cpanm --local-lib=$HOME My-Module-*.tar.gzThis exercises the packaged distribution and its declared dependencies through the normal CPAN installation path rather than through the project's development build environment.
- Put Your Project Under Source Control
-
Initialize the repository and stage the project files recommended by the build system:
make gitBy default,
make gitcreates the initial commit after staging the managed build files, project configuration, editable source files, and other tracked project artifacts.If you want to initialize and stage the project without creating the commit yet, use:
make git NO_COMMIT=1 - Learn More About
CPAN::Maker::Bootstrapper -
See "EXTENDING THE BUILD SYSTEM" for adding project-specific build logic and customizing the managed build framework.
See "Dependencies Management" for details on dependency discovery, classification, filtering, and generated dependency artifacts.
See "FAQ" for common questions and practical recipes.
WHY YOU SHOULD CONSIDER USING CPAN::Maker::Bootstrapper
Many build systems place the build procedure inside the CI platform: workflow files describe the steps, the CI runner executes them, and a separate local workflow is needed to reproduce the same build.
CPAN::Maker::Bootstrapper takes the opposite approach.
The build belongs to the project.
The same dependency graph, quality gates, generated artifacts, tests, and distribution rules run whether make is invoked on your laptop, on a remote host, inside make build-ci, or from GitHub Actions.
CI is therefore a caller of the build system rather than the place where the build system lives.
A fresh checkout remains directly buildable with:
git clone <repository>
cd <project>
make
The CI layer can add isolation and automation, but it does not define a second build procedure that must be kept synchronized with local development.
The Stack
The build system is built on three tools with deliberately different responsibilities:
GNU make - models causality.
Targets, prerequisites, timestamps, pattern rules, and order-only dependencies describe what must be rebuilt and why. Make decides what work is necessary.
bash - performs process orchestration.
Shell recipes connect command-line tools, manage files and temporary state, and express the small imperative steps needed to carry out a build action.
Perl - handles transformations that deserve a real programming language.
Dependency analysis, metadata generation, configuration processing, distribution assembly, and other non-trivial transformations remain readable and testable Perl rather than growing into increasingly complex shell fragments.
CPAN::Maker::Bootstrapper provides the conventions that connect those layers.
GNU make owns the dependency graph, bash performs the orchestration, and Perl implements the complex transformations. The result is a build system that remains visible and auditable: make -n shows what would run, bash -x exposes shell execution, make help documents the available targets, and project.mk provides an upgrade-safe place for project-specific behavior.
Best Practices Out of the Box
The installed build system makes several professional development practices part of the project structure without forcing them on the developer on every build:
Editable source and generated artifacts are distinct - .pm.in and .pl.in files are the project sources; the corresponding .pm and .pl files are generated artifacts. Changes belong in the .in files and are propagated by
make.Dependencies are derived from the source -
scandeps-static.plmaintains requires, test-requires, recommends, and suggests as the source changes. Pinning, sticky entries, and skip lists allow the generated dependency model to be adjusted when necessary.Quality checks are part of the dependency graph - syntax checking,
perltidy,perlcritic, and POD validation are build gates rather than separate release-time procedures. The gates remain independently controllable when a project needs different development or runtime behavior.Managed build policy remains upgradeable -
make updaterefreshes the build framework installed beneath .includes/, whilemake upgradechecks for and installs newer versions ofCPAN::Maker::Bootstrapper.Project-specific behavior stays outside managed files - project.mk is the upgrade-safe extension point for custom targets, additional dependencies, lifecycle hooks, and project-specific variables. Projects extend the framework without modifying the files maintained by the bootstrapper.
Perl Quality Tools
The build system supports syntax checking, perltidy, and perlcritic as build-time quality gates.
Syntax checking is controlled independently with SYNTAX_CHECKING:
make SYNTAX_CHECKING=OFF
Perltidy and perlcritic are controlled by LINT, which acts as the master switch for both tools:
make LINT=OFF
The tools may also be disabled individually:
make PERLTIDY=""
make PERLCRITIC=""
This allows one linting tool to remain enabled while the other is disabled.
PERLTIDYRC and PERLCRITICRC configure the corresponding tools; they do not enable or disable them.
For example:
make PERLTIDYRC=.perltidyrc
make PERLCRITICRC=.perlcriticrc
If no profile is specified, the corresponding tool uses its default configuration.
If a profile variable names a file that does not exist, the operation fails.
Modules or scripts that cannot be syntax-checked outside their runtime environment may be added to PERLWC_SKIP in project.mk:
PERLWC_SKIP = bin/startup.pl
Inter-module build dependencies may also be declared explicitly in project.mk when they cannot be inferred automatically:
lib/Foo/Bar.pm: lib/Foo.pm
make quick provides a fast development build that disables distribution dependency scanning and both lint tools while leaving syntax checking enabled:
make quick
This is equivalent to:
make SCAN=OFF LINT=OFF
A GNU Make Tutorial in Disguise
Because the build system is implemented with ordinary GNU make, the managed files beneath .includes/ are also working examples of the techniques used to model a non-trivial build.
They include:
Pattern rules and sentinel files for incremental quality gates
define/endefblocks for reusable shell and Perl fragments$(shell ...),$(eval ...),$(call ...),$(filter-out ...),$(addprefix ...), and$(patsubst ...)for deriving and transforming build state?=,:=,+=, and=with their distinct evaluation semanticsOrder-only prerequisites,
.DEFAULT_GOAL,-include, and.SHELLFLAGS := -ecmktemp, shell traps, and bash conditionals inside recipesPerl programs embedded in make variables when a transformation is better expressed in Perl than in shell
The build system is deliberately transparent to not only reveal how the sausage is made but to expose these techniques to the developer so you can incorporate them in your recipes. However, the build sysetm framework itself is read-only to discourage you from tampering with a complex set of recipes that have been carefully crafted and can easily be broken. Your extension point remains the project.mk file and the double-colon targets.
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 .
Determining the Primary Module
The bootstrapper determines the primary module name in the following order:
- 1.
--module -
If
--moduleis supplied, that value is used. - 2. Custom stub
-
If no module name was supplied and
--stubnames a file, the first package found in that file is used. - 3. Installation directory
-
If the module name is still unknown and
--installdirwas supplied, the bootstrapper derives the module name from the installation directory name. - 4. Single import directory
-
If the module name is still unknown and exactly one
--importpath was supplied, the bootstrapper derives the module name from the basename of that directory.For example:
cd Foo-Bar cmb --import .infers
Foo::Bar.
When deriving a module name from a directory name, hyphens are converted to ::, so Foo-Bar implies Foo::Bar.
The resulting name must be a valid Perl module name.
When importing an existing project, the corresponding module file must also exist beneath one of the import paths. For example, Foo::Bar must be found as Foo/Bar.pm somewhere beneath one of the directories supplied with --import.
What Gets Imported
When importing an existing project, the bootstrapper scans each --import directory and builds an explicit import plan.
The following files are recognized:
Perl modules
Files ending in .pm are imported as distribution modules and placed beneath lib/.
For example:
lib/Foo/Bar.pmbecomes:
lib/Foo/Bar.pm.inThe package declared by the module determines its destination beneath lib/.
Perl scripts
Files ending in .pl are imported beneath bin/ and converted to generated source files ending in .in.
Executable files
Executable files not otherwise classified are imported beneath bin/ and converted to generated source files ending in .in.
Test files and test helpers
Files beneath the following recognized test directories are preserved in place:
t/ xt/author/ xt/release/ xt/smoke/Within those directories, files with the following extensions are treated as test material:
.pm .pl .t .sh .datThe directory takes precedence over the file extension. For example:
t/lib/TestHelper.pmremains:
t/lib/TestHelper.pmand is not imported as:
lib/TestHelper.pm.inLikewise:
xt/author/check.plremains beneath xt/author/.
Change logs
The following root-level change log files are preserved when present:
ChangeLog CHANGELOG Changes CHANGESTheir original names are retained.
Files not matching one of the recognized categories are not imported.
Excluding Import Paths
Use --exclude to omit directories beneath an import root.
The option may be supplied more than once:
cmb --import .
--exclude local
--exclude Foo-Bar-1.2.3
Each exclusion is interpreted relative to the import root and excludes that directory and everything beneath it.
For example:
--exclude local
excludes:
local/
local/lib/
local/bin/
but does not exclude an unrelated directory with the same name outside the import root.
The following source-control directories are always excluded and do not need to be specified explicitly:
.git
.hg
.svn
These directories are pruned wherever they occur beneath an import root.
Previewing an Import
Use --dry-run to inspect the import plan without modifying the filesystem or running the generated build.
For example:
cmb --import .
--exclude local
--dry-run
The bootstrapper scans the import directories, applies exclusions, classifies the recognized files, determines their destinations, and prints the resulting import plan.
No installation directory is created, no files are copied, and make is not run.
This is useful when importing an existing project because it allows the proposed mapping to be reviewed before any project files are generated.
Creating a Project Tarball
Use --project-tarball to create an archive containing the complete generated CPAN::Maker::Bootstrapper project instead of installing that project into a directory.
For example:
cmb --import .
--exclude local
--project-tarball
The bootstrapper performs the normal import and build process in a temporary working directory and then writes a project archive to the directory from which cmb was invoked.
For a module named Foo::Bar, the resulting archive is named:
Foo-Bar-cmb.tar.gz
The archive contains a top-level project directory:
Foo-Bar/
and includes the generated CPAN::Maker::Bootstrapper project, including the Makefile, build configuration, imported source files, test files, build support files, logs, and the generated CPAN distribution tarball.
This is different from the CPAN distribution tarball produced by the build. The CPAN distribution contains the files intended for release to CPAN; the project tarball contains the complete CPAN::Maker::Bootstrapper development project used to build that distribution.
The temporary dependency installation directory used during the import build is not included in the project archive.
Import Destination Safety
When importing an existing project into a directory, the bootstrapper refuses to create the generated project inside one of the directories being imported.
For example, this is not allowed:
cmb --import . --installdir ./converted
when converted/ would be created beneath the import root.
The bootstrapper also refuses to use the import root itself as the installation directory.
These checks prevent the generated project from becoming part of the source tree while that source tree is being scanned and imported.
--force does not override this safety check.
The check applies only when installing the generated project into a directory. --project-tarball does not create an installation directory and therefore does not require this restriction.
Import Build Policy
After constructing the imported project, the bootstrapper runs the generated build to verify that the project can be built successfully.
The import build uses the following defaults:
SCAN=on
SYNTAX_CHECKING=on
LINT=off
Dependency scanning and syntax validation therefore remain enabled during import.
Linting is disabled by default because importing an existing project should not require that project to satisfy the bootstrapper's perltidy or perlcritic policy before it can be converted.
These defaults may be overridden through the corresponding environment variables.
For example:
LINT=on cmb --import .
enables linting during the import build.
This setting applies only to the bootstrap import build. The generated project retains its normal build configuration and may enable linting for subsequent make invocations.
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:
- 2. 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.yml file.
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 - 3. Initialize a git repository with
make git - 4. Run
make tidyif you want to format the imported source -
make tidyPerltidy uses
PERLTIDYRCwhen configured and otherwise uses its default configuration. - 5. Run
maketo produce the final distribution tarball -
By default the generated build performs syntax checking, dependency scanning, perltidy, and perlcritic.
Dependency scanning may be disabled with:
make SCAN=OFFPerltidy may be disabled independently with:
make PERLTIDY=""Perlcritic may be disabled independently with:
make PERLCRITIC=""Both lint tools may be disabled together with:
make LINT=OFFTo disable dependency scanning and both lint tools while retaining syntax checking, use:
make quick - 6. Test installation
-
cpanm -n -v ./My-Script-1.0.0.tar.gz
Limitations
--importcannot be used with--stub- they are mutually exclusive ways to create the initial sourceThe importer uses the package declarations inside
.pmfiles to determine where to place them under lib/. If the importer cannot match the filename with a package declaration inside the file, it will warn and skip that fileImported files are not tidied automatically.
Run
make tidyafter import if you want to format the imported source.If
PERLTIDYRCis configured, that profile is used. Otherwise perltidy runs with its default configuration.Inter-module dependencies are normally detected automatically. The build generates deps.mk from dependencies between modules in the distribution so prerequisite modules are built before syntax checking modules that depend on them.
If a dependency cannot be inferred automatically, declare it explicitly in project.mk:
lib/My/Script.pm: \ lib/My/Script/Role/Frobnicate.pm \ lib/My/Script/Role/List.pmSee "Inter-module dependencies" for details.
Importing a CLI::Simple Scaffold Tarball
Suppose you have a project that used CLI::Simple as base class and now want to use the CPAN::Maker::Bootstrapper framework.
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 configuration from your global .gitconfig or from a separate .ini file. Configuration values are used when scaffolding distributions and by the AI-assisted commands.
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
When --installdir is not supplied, the bootstrapper uses basedir from the configuration when one is defined. Otherwise, it uses the current working directory as the base directory for the new project.
An explicit --installdir always takes precedence.
A separate configuration file may contain entries such as:
[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-helper-
For 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.You can generate a starter configuration with:
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 actually 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 dependency scanning during
make. Set toOFFto disable distribution dependency scanning. The default isON.
INSTALLED PROJECT FILES
The following files are installed into the project directory:
Makefile- the complete build system. Derives project paths and names fromMODULE_NAME, the package name in a custom stub, or the project directory 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.tmplorcli-module.pm.tmplwhen--stub cliis used.Note: Files under lib/ and bin/ use
.pm.inand.pl.inas editable sources. The generated.pmand.plfiles are derived from them by the Makefile and will be overwritten by subsequent builds.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 withmake update..includes/bootstrap.mk - used internally by the bootstrapper .includes/bash-completion.mk - make bash-completion target .includes/modulino.mk - make modulino target .includes/git.mk - make git target .includes/help.mk - make help target .includes/local.mk - vendors dependencies for syntax checking .includes/perl.mk - pattern rules, syntax checking, tidy, critic .includes/publish.mk - publish to CPAN .includes/release-notes.mk - make release-notes target .includes/update.mk - make update target .includes/upgrade.mk - make upgrade/check-upgrade targets .includes/version.mk - make release/minor/major targetsproject.mk - your extension point for custom make rules, inter-module dependencies, and project-specific variables. Never touched by
make update. See "EXTENDING THE BUILD SYSTEM".modulino.tmpl - template used by
make modulinoto generate bash wrapper scripts for modulino-style modules.VERSION - contains the current version string in
major.minor.patchformat. Managed bymake release,make minor, andmake major.ChangeLog - empty placeholder, required by the distribution.
.prompts/
The directory is created automatically the first time
pod-revieworcode-reviewneeds the default prompt files.
THE PROJECT MAKEFILE
The installed Makefile is self-configuring. It can derive the primary module from MODULE_NAME, the package name inside a custom stub, or the project directory name.
For example, a primary module of My::New::Module produces:
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 all-
Builds the distribution tarball. When dependency scanning is enabled, updates requires, test-requires, recommends, and suggests, and generates README.md as prerequisites.
make bash-completion-
Generates and installs a bash completion function for your project's modulino, then prints the
sourceline to enable it. The function is produced by<modulino> -generate-completion(available to anyCLI::Simple-based modulino) and written to ~/.local/share/bash-completion/completions/<alias>.make bash-completion # then, as it instructs: source ~/.local/share/bash-completion/completions/<alias>The target depends on the modulino, so it will build bin/<alias> first if needed. Completion is only available for modulinos that subclass
CLI::Simple. make help-
Lists the available build targets and commonly used build variables. Project-specific targets in project.mk are included when their target definition contains a
##description. make requires/make test-requires-
Scans source files with
scandeps-static.pland writes the dependency files specified in thebuildspec.ymlfile used bymake-cpan-dist.pl.Any change to your
.pm.infiles will trigger a rescan of your modules for new dependencies. This can add a significant delay when you have many modules and a large number of dependencies. You can avoid the scan if you know that no new dependencies have been added by setting the environment variableSCANtoOFF(case insensitive).make SCAN=OFFYou can make scanning deliberate by adding
SCAN=OFFto your config.mk file. Then, to rescan:make SCAN=ON make recommends/make suggests-
Companion targets to
make requires. The dependency scanner classifies each discovered module into one of three tiers: hardrequires,recommends(soft, non-eval conditional dependencies), andsuggests(eval-wrapped, optional dependencies).These files are consumed by CPAN::Maker when it generates the distribution metadata, including the corresponding dependency sections in Makefile.PL. See "Dependencies Management".
DARKPAN_REQUIRES-
Set
DARKPAN_REQUIRESto a true value (1,yes,on, orsi) to generate dependency manifests for modules available from a configured DarkPAN.This is useful when a distribution published to CPAN has one or more runtime dependencies that are intentionally hosted on a separate CPAN-compatible repository. CPAN metadata can still declare those dependencies normally, but standard installers need additional information to locate distributions that should be obtained from the DarkPAN.
The generated DarkPAN manifests provide that information without duplicating the dependency declarations maintained in requires. They are included in the distribution as installation aids for the person or process installing the module. They are not automatically consulted by Perl installers during a normal installation; the installer must explicitly use the appropriate manifest or configure the DarkPAN repository.
When enabled,
DARKPAN_URLmust specify the base URL of the CPAN-compatible repository:DARKPAN_REQUIRES = yes DARKPAN_URL = https://cpan.example.com/repositorymakeexamines requires and generates:cpanfile.darkpan cpanm.darkpanWhen
DARKPAN_REQUIRESis enabled, cpanfile.darkpan and cpanm.darkpan are added to buildspec.yml as extra files and are therefore included in the distribution. They are expected to be tracked by git unless the developer explicitly adds them to extra-files.skip.For each module listed in requires, the build checks whether the module is available from the configured DarkPAN. Modules found there are added to the generated DarkPAN manifests.
For modules available from the DarkPAN, the build also checks MetaCPAN. If a module is available from both CPAN and the DarkPAN, the DarkPAN version is preferred and the module remains in the generated manifests. This allows the DarkPAN to provide a version of a module that is also published on CPAN.
Modules that should not be obtained from the DarkPAN may be listed in darkpan.skip, one module name per line. Those modules are omitted from both generated DarkPAN manifests.
cpanfile.darkpan contains dependencies selected for resolution from the DarkPAN in cpanfile syntax. cpanm.darkpan contains the same dependencies in a form suitable for passing to cpanm.
The configured DarkPAN must publish:
modules/02packages.details.txt.gzunder
DARKPAN_URL. make package-
Runs the quality and dependency gates together (
lintplus a dependency scan) - a convenience for pre-release verification. TARBALL_ORDER_ONLY_PREREQS-
Additional order-only prerequisites for the distribution tarball.
Set this in project.mk when project-specific generated artifacts or other preparation steps must complete before the tarball is built but should not themselves determine whether the tarball is out of date.
TARBALL_ORDER_ONLY_PREREQS += prepare-assets make release/make minor/make major-
Bumps the patch, minor, or major version number in
VERSION. make release-notes-
Generates a diff, file list, and tarball comparing the current version to the previous git tag.
make clean-
Removes build artifacts registered for cleaning. Does not affect buildspec.yml, VERSION, or any
*.insource files.If your project needs a project-specific clean recipe, use the
clean-localtarget with a double-colon.clean-local:: rm -rf workdir make test-
Runs the project's distribution unit tests under t/:
prove -I lib -I local/lib/perl5 -v t/make testalso runs any project-specifictest-local::recipes defined in project.mk.Projects may have tests that exercise development infrastructure, external services, generated artifacts, or other behavior that should not be included in the CPAN distribution. These can be added through the
test-local::extension point:test-local:: ./bin/test-integrationThe double-colon form allows project.mk to extend the managed
test-localtarget without replacing it.make testalso recognizes the conventional extended-test directories xt/author/, xt/release/, and xt/smoke/. These test suites are not run by default, but may be enabled through the corresponding environment or make variables:AUTHOR_TESTING=1 make test RELEASE_TESTING=1 make test AUTOMATED_TESTING=1 make testWhen enabled,
make testinvokes the corresponding test target after the normal t/ test suite andtest-local::recipes have completed. -
Runs tests under xt/author/:
prove -I lib -I local/lib/perl5 -r xt/authorThe xt/author/ directory is created automatically if it does not already exist.
This target uses a double-colon rule and may therefore be extended in project.mk without replacing the managed target:
test-author:: ./bin/check-generated-docs make test-release-
Runs tests under xt/release/:
prove -I lib -I local/lib/perl5 -r xt/releaseThe xt/release/ directory is created automatically if it does not already exist.
The target may be extended in project.mk using
test-release::. make test-smoke-
Runs tests under xt/smoke/:
prove -I lib -I local/lib/perl5 -r xt/smokeThe xt/smoke/ directory is created automatically if it does not already exist.
The target may be extended in project.mk using
test-smoke::. make test-all-
Runs the complete test suite: distribution tests under t/, any project-specific
test-local::recipes, and the author, release, and smoke test suites.It is equivalent to running:
make test AUTHOR_TESTING=1 RELEASE_TESTING=1 AUTOMATED_TESTING=1The extended test directories follow established Perl distribution conventions.
CPAN::Maker::Bootstrapperpreserves those conventions rather than requiring imported or existing projects to reorganize their tests. make tidy-
Runs
perltidyon all.pm.inand.pl.insource files.If
PERLTIDYRCis set, the named profile is used:make tidy PERLTIDYRC=.perltidyrcIf
PERLTIDYRCis not set, perltidy runs using its default configuration.If
PERLTIDYRCnames a file that does not exist, the target fails.The target also performs syntax checking before modifying the source files.
make critic-
Runs
perlcriticon the project's Perl source files.If
PERLCRITICRCis set, the named profile is used:make critic PERLCRITICRC=.perlcriticrcIf
PERLCRITICRCis not set, perlcritic runs using its default configuration.If
PERLCRITICRCnames a file that does not exist, the target fails.The target also honors:
PERLCRITIC_THEME PERLCRITIC_SEVERITYand performs syntax checking before running perlcritic.
make lint-
Runs both linting targets:
make tidy make criticThe perltidy and perlcritic configuration variables described above apply to their respective targets.
make git-
Initializes a git repository, stages all recommended project files including .includes/*, and makes an initial
BigBangcommit. make quick-
Builds the distribution tarball with distribution dependency scanning and perltidy/perlcritic disabled. Syntax checking remains enabled.
Useful during active development when you want fast iterative builds without updating requires, test-requires, recommends, or suggests.
make quickEquivalent to:
make SCAN=OFF LINT=OFF make workflow-
Installs a CI build script (builder), its default environment file (builder.env), and a GitHub Actions workflow (.github/workflows/build.yml) into your project, templated with your module and project name. Also merges any build-only dependencies
builderneeds into build-requires.make workflow git add build-requires builder builder.env .github/workflows/build.ymlCommit these files - GitHub Actions will then run
./builderon every push tomainordev. See "Continuous Integration" for whatbuilderdoes, how to customize its environment and build lifecycle, and how to run it outside of GitHub Actions. make build-ci-
Runs builder locally inside Docker, against your current working tree, to reproduce a CI build without pushing. Requires
dockerand a builder script (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 to generate README.md from a custom source, 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 - deps-filter
-
cmb deps-filter requiresFilters a dependency list so that modules already provided by another listed distribution are removed.
The command consults the public CPAN package index and any repositories listed in build-mirrors. Repository indexes are cached under the user's cache directory and conditionally refreshed on subsequent runs.
This command is normally invoked automatically by the generated Makefile for
requires,recommends,suggests, andtest-requires. - dist-file
-
cmb dist-file distribution-name filenameCopies a distribution file to STDOUT. Searches the root and share/ directories of the distribution for file. Throws and exception if either the file is not found or the distribution is invalid.
Example:
cmb dist-file CPAN-Maker-Bootstrapper builder.env - extra-files
-
cmb extra-files path file1 file2 ...Adds files to the distribution. Use
.for files that should appear at the root of the distribution tarball but not be installed into the share directory. Usesharefor files that should be installed into 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 cmb extra-files share share/config.jsonEntries may be removed by editing buildspec.yml directly, which is usually the clearest approach.
The
cmb extra-filescommand also supports removing an entry by prefixing the filename with-:cmb extra-files . -README.mdThis is primarily useful from scripts or other automated workflows.
- create-deps
-
cmb create-deps [module.pm.in ...]Emits GNU make dependency rules (to STDOUT) capturing the inter-module dependencies within your distribution -- i.e. which of your own
.pmfilesusewhich others. Uses Module::ScanDeps::Static to scan each source module, then printstarget: prerequisitelines (in deps.mk form) for the internal packages only, somakerebuilds a dependent module when a module it depends on changes. With no arguments every project module is scanned; name one or more modules to restrict the output. - create-darkpan-requires
-
cmb create-darkpan-requires [--filter file] [requires-file]Examines requires (or
requires-file) and identifies dependencies available from the configured DarkPAN. Each dependency is checked against the DarkPAN 02packages.details.txt.gz index. Dependencies found on the DarkPAN are included in the generated manifests.For each dependency found on the DarkPAN, MetaCPAN is also checked. If the module is available from both CPAN and the DarkPAN, a warning is emitted and the DarkPAN is preferred.
The optional
--filterargument names a file containing module names to exclude from the generated manifests, one module per line:cmb create-darkpan-requires --filter darkpan.skip requiresThis is useful when a module is available from both CPAN and the DarkPAN but the distribution author wants that dependency to be resolved from CPAN.
When invoked through the generated Makefile, darkpan.skip is used automatically when it exists.
The presence of darkpan.skip affects only which dependencies are written to the manifests; it does not change the distribution or source-control treatment of the generated files.
The generated files are intended as installation aids and are included with the distribution. They do not alter normal Perl dependency resolution by themselves and are not automatically consulted during installation. Instead, they are intended to be consumed explicitly by your installation tool, such as
cpmorcpanm.For distributions that include these files, the
cpan-distfileutility provided with DarkPAN::Resolver::SQLite can be used to retrieve them directly from a CPAN distribution without manually downloading and unpacking the tarball.cpan-distfile Some::Module cpanm.darkpan > cpanm.darkpanSee DarkPAN::Resolver::SQLite for examples of using these manifests with
cpmandcpanm.The command generates two representations of those dependencies:
cpanfile.darkpan cpanm.darkpancpanfile.darkpan uses cpanfile syntax:
requires 'Amazon::API::CloudWatchLogs', '1.43.90';cpanm.darkpan contains one cpanm module requirement per line:
Amazon::API::CloudWatchLogs~1.43.90The version constraints are taken from requires; the DarkPAN index is used only to determine whether a module is available from a DarkPAN repository.
This command is normally invoked automatically by
makewhenDARKPAN_REQUIRESis enabled. - critique
-
cmb critique file ... cmb critique --file-list manifestRuns Perl::Critic over the given files (or a newline-delimited
--file-list). Defaults to thepbptheme at severity 5; override with thePERLCRITIC_THEME,PERLCRITIC_SEVERITY, andPERLCRITICRCenvironment variables. Requires Perl::Critic to be installed. - publish-to-cpan
-
cmb publish-to-cpan distribution.tar.gz [username [password]]Uploads a distribution tarball to PAUSE.
The username and password may be supplied as arguments or through
PAUSE_USERandPAUSE_PASSWORD. Normally this command is invoked bymake publish, which rebuilds and tests the distribution before uploading it. - resolve-vars
-
cmb resolve-vars [--vars-file FILE] [--no-strict] source-fileFilters
source-fileto STDOUT, substituting@TOKEN@placeholders with values drawn from the environment (or from a--vars-file). This is the mechanism the generated Makefile uses to turn .pm.in and .pl.in sources into their built.pm/.plcounterparts -- for example filling2.4.0from the VERSION file or@BUILD_DATE@at build time.A placeholder is required to have a value only when it appears in live code. Placeholders that occur solely inside POD or
#comments are treated as references, not substitutions: they never trigger a "no value present" error and are left untouched when no value is available. This lets you document a token in your POD (e.g. mention@BUILD_DATE@in a description) without breaking the build.For placeholders that do appear in code, behavior depends on
--strict(the default):strict (default) - a placeholder in code with no value is a fatal error; the build stops.
--no-strict - a placeholder in code with no value produces a warning and is left in place literally (as
@TOKEN@) rather than being substituted to an empty string.
See "
--vars-file" and "--strict, --no-strict".
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 disposition. 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 are 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 suitable for placement 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 - show-defaults
-
Prints the resolved default option values to STDOUT after applying configuration-file values and runtime defaults.
- update-annotations
-
cmb update-annotations fileApplies human-curated annotations to the most recent code review for
file. On first run it generates an .annotate file alongside the review for you to edit; re-run it to apply your edited annotations back into the review. Pairs withcode-review/annotatein the review workflow.
OPTIONS
--annotate|-aN:DISPOSITION--auto-annotate|-A--basedir|-bDIR-
Base directory in which to create the project. 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-color-
default: color
To turn color off use --no-color.
--dry-run|-D-
Dry run mode will abort after displaying a pre-submission token and cost estimation for the
pod-reviewandcode-reviewcommands. --config|-cconfiguration file-
The path to a
.inifile that contains configuration information used to scaffold your project.default: ~/.gitconfig
--context|-CPATH-
One or more files to submit with your code review file that provide additional context for the LLM during the review.
--email|-eEMAIL-
Override the author email. Defaults to
user.emailfrom your global git config. --finalize-annotations|-F--force|-f-
Overwrite an existing project. Without this flag, the command dies if a
Makefilealready exists in the target directory. --github-user|-gUSER-
Override the GitHub username used to construct repository URLs in
buildspec.yml. Defaults touser.githubfrom your global git config. --import|-Ipath-
A 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/binThe primary module must be determinable from either directory name or supplied using the
--moduleoption. The corresponding module file must exist beneath one of the import paths. For example,Foo::Barmust be found as Foo/Bar.pm.The Makefile will automatically attempt to substitute the token
@PACKAGE_VERSION@inside your.pl.inor.pm.infiles with the current semantic version in the VERSION file. If you want to use that for versioning your scripts and modules add the token as shown below:C<our $VERSION = 'E<64>PACKAGE_VERSIONE<64>';>
--installdir|-iDIR-
Directory in which to create the project. When supplied, this overrides the configured or command-line
basedir. 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-filesLIMIT-
The maximum number of changed files included in the release artifact that may be uploaded to the LLM when generating release notes. Set to
0for no limit.default: 50
--max-tokens|-tTOKENS-
Maximum 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|-MMODEL-
Specifies the model id to use for the
pod-reviewandcode-reviewcommands.For
pod-reviewthe default model isclaude-haiku-4-5-20251001.For
code-reviewthe default model 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-
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 may omit this option when the module name can be determined from a custom stub file or from the project directory name.
--prompt|-pPATH-
Path 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|-PNAME-
The name of a prompt profile located in the .prompts directory. One or more profile names may be specified. You need only provide the name (e.g. cli-tool).
--resources|-rgithub-
Currently takes only a single value: 'github' that indicates that the resources section of Makefile.PL should be populated with GitHub URL references. Future versions may support additional providers.
--strict,--no-strict-
Controls how
resolve-varstreats an@TOKEN@placeholder that appears in code but has no value in the environment or--vars-file.--strict(the default) makes this a fatal error.--no-strictdowngrades it to a warning and leaves the placeholder literal in the output.This affects code placeholders only. Placeholders that appear solely in POD or
#comments are always ignored by the missing-value check regardless of this flag, so--no-strictis not needed merely to document a token. --stub|-sTYPE|PATH-
Controls 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. --username|-uNAME-
Override the author name used in the module stub and
buildspec.yml. Defaults touser.namefrom your global git config. --vars-file-
The path to a file containing template variable values used by
resolve-vars.
THE REVIEW WORKFLOW
CPAN::Maker::Bootstrapper allows you to 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:
- 1. Run a review
-
cmb code-review --prompt-profile cli-tool lib/My/Module.pmThe review is written to a timestamped
.codefile containing a JSON object withfindings,confirmations, anddeferredarrays. - 2. Annotate the findings
-
An annotation is how you mark a finding with a disposition. The dispositions are used by the LLM during the next 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.
Alternatively, use
update-annotationsto maintain dispositions in an annotation file rather than on the command line:cmb update-annotations lib/My/Module.pmThe first invocation creates an .annotate file for editing. Run the command again after editing the file to apply those dispositions to the review.
- 3. 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 the prompt and code are submitted for review, the script displays estimated token usage and cost. The input token count is obtained from the model's token-counting API using the message that will actually be submitted, so the input count is accurate. The output token count, and therefore the final cost, is an estimate.
To stop before submitting the review, use --dry-run. The command will abort immediately before the message is sent to the LLM.
Dispositions
Each finding in the annotations file must be given one of the dispositions described below before the next review can be submitted. The prompt sent to the LLM is designed around these dispositions. This helps successive reviews converge by carrying forward the developer's decisions from earlier rounds.
- 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 review should be considered complete.
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
Review cost depends on the selected model, source size, prompt profiles, and number of findings. Costs generally 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 project-specific prompt profiles to .prompts/ and commit them with 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 are appended verbatim to the base review prompt. The built-in profiles use one instruction per line, typically prefixed with -.
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.
Additional Profile Ideas
- 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 installed Makefile and files under .includes/ are managed by CPAN::Maker::Bootstrapper. They are intentionally write-protected and may be replaced by make update when the bootstrapper is upgraded.
Project-specific build logic belongs in project.mk, which is always writable and is never touched by make update. This provides an upgrade-safe extension point for project-specific targets, variables, and build ordering.
The managed include files live in the .includes/ directory, where they are write-protected and clearly separated from project files. The Makefile includes them automatically:
include .includes/publish.mk
include .includes/bootstrap.mk
include .includes/perl.mk
include .includes/local.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 .includes/bash-completion.mk
include .includes/modulino.mk
These files are included if they exist:
include config.mk
include project.mk
include extra-files.mk
Immutability Is a Feature
The managed build system is deliberately immutable: the Makefile, everything under .includes/, and the generated .pm/.pl files are write-protected on purpose. This is a feature, not a restriction. It lets make update replace those files with newer, better versions without clobbering anything of yours, and it guarantees that two projects on the same bootstrapper version use the same managed build rules -- there is no per-project drift hiding in a locally edited managed rule.
You can override any of it -- these are your files, and nothing stops you from chmod +w and editing a generated module or a managed include. But you should not need to, and if you do, make update will overwrite your change. Every legitimate customization has a sanctioned hook that survives make update:
Project-specific targets, rules, and build ordering -- project.mk (always writable; never touched by
make update).Reach for project.mk when your project needs to do something the generic build system can't know about, for example:
Build a companion artifact the managed build doesn't produce -- generate a .pm.in from a JSON/YAML schema, render documentation, compile assets, or (as
Amazon::APIdoes) build a Storable data file consumed at runtime.Declare inter-module build order the scanner can't infer --
lib/Foo/Bar.pm: lib/Foo.pmwhen one module must be built before another.Deploy or publish -- an
scp/upload/notify target that runs afterall.Extend cleanup -- a
clean-local::double-colon rule to remove your own generated files, andCLEANFILES +=for anything else.
See "What Belongs in project.mk" for worked examples of each, and "What Does NOT Belong in project.mk" for the line between your extensions and the managed core.
Build-behavior toggles (dependency scanning, linting, syntax checking, version-drift strictness) -- make variables set on the command line or in config.mk (see "CONFIGURATION" and the variable list below).
Template tokens in your source -- declare them in
TEMPLATE_VARSand letcmb resolve-varsfill them, rather than hand-editing a generated .pm (see "Custom Template Tokens").Extra distribution files -- list them in buildspec.yml;
extra-files.mkwires them into the tarball automatically.Dependencies the scanner cannot see -- the sticky
+prefix in requires (see "Dependencies Management").
If you find yourself wanting to edit a managed file, check this list first: the hook you need almost certainly exists, and using it keeps you on the upgrade path instead of forking the build system.
Why the generated .pm/.pl files are read-only: they are regenerated from their .pm.in/.pl.in sources when its prerequisites require regeneration, so any edit you make directly to a generated .pm would be silently lost on the next make. The chmod -w is there to stop you from making that mistake. Edit the .pm.in source, not the generated .pm.
How the Makefile Works
The installed Makefile is structured around a few key concepts:
Source files live in lib/ as .pm.in and in bin/ as .pl.in. The build generates the final .pm and .pl files from these sources by substituting
@PACKAGE_VERSION@and other tokens, running syntax checks, and optionally running perltidy and perlcritic.Sentinel files - the build uses sentinel files to track incremental quality-gate state. .checked records successful syntax and validation checks, .tdy records successful perltidy processing, and .crit records successful perlcritic processing. Each sentinel is regenerated only when the source or the prerequisites for that gate change.
Dependency scanning -
scandeps-static.plscans your source files and maintains the dependency files used by CPAN::Maker when generating distribution metadata, including requires, test-requires, recommends, and suggests. Controlled bySCAN=ON|OFF.The distribution tarball is the final output of
make. It is built bymake-cpan-dist.plusing buildspec.yml.Inter-module dependency discovery - the build scans modules within the distribution and generates deps.mk so
makecan build modules in dependency order before syntax checking them. Dependencies that cannot be inferred automatically may be added in project.mk.
Key build variables you can override on the make command line or in config.mk:
SCAN=OFF- skip distribution 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 .pod file or strip it entirely from the built .pmPERLINCLUDE="-I path"- additional include paths used during theperl -wcsyntax check. Defaults to-I lib -I local/lib/perl5for hermetic checking; see "The local dependency library".CPAN_INSTALLER=cpm|carton- selects the installer used to populate local/ for hermetic syntax checking. Auto-detected (cpmpreferred) if unset.
Two further toggles, CMB_UPDATE_CHECK and CMB_VERSION_DRIFT, are set in config.mk rather than on the command line; see "Automatic Drift and Update Checks". config.mk is read on every invocation of make and is the right place for durable, machine- or project-wide build settings such as SYNTAX_CHECKING=OFF on a box without an installer.
What Belongs in project.mk
- Custom targets
-
Any target specific to your project - generating assets, running linters, deploying, sending notifications:
.PHONY: deploy deploy: all ## deploy the distribution scp $(TARBALL) user@myserver:/opt/cpanAdd
##followed by a description to a target definition to include the target in the output frommake help. Because project.mk is included inMAKEFILE_LIST, project-specific targets are discovered automatically:make helpThere is no separate help table to maintain.
- 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 target
-
clean-local:: rm -rf workdir - Extending the test recipe
-
Projects may have development or integration tests that should not be included in the CPAN distribution. Add them to
make testby extendingtest-localwith a double-colon rule:test-local:: prove -I lib -v xt/
What Does NOT Belong in project.mk
Modifications to existing targets like
all,clean,requiresReplacing managed variables such as
DEPSorCLEANFILES.Use documented extension points such as
CLEANFILES +=where provided rather than redefining the managed value.Anything that duplicates logic already in the managed Makefile
Custom Template Tokens
The build generates each .pm/.pl from its .pm.in/.pl.in source by substituting @TOKEN@ placeholders through cmb resolve-vars. The standard tokens (@PACKAGE_VERSION@, @MODULE_NAME@, @GIT_SHA@, and the other git-metadata variables) are always available, but the mechanism is extensible: a project can define its own tokens without editing any managed file.
To add a token, declare its name in TEMPLATE_VARS (in project.mk) and provide a value -- as a make variable, an exported environment variable, or through the variables file passed to resolve-vars. For example, to stamp a build timestamp:
# in project.mk
BUILD_DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
TEMPLATE_VARS += BUILD_DATE
# in a .pm.in source
our $BUILD_DATE = 'E<64>BUILD_DATEE<64>';
cmb resolve-vars then fills @BUILD_DATE@ from the value when its prerequisites require regeneration. The token grammar is uppercase-only (@[A-Z0-9_]+@), so placeholders never collide with real Perl such as @_ or @ISA.
By default, substitution is fail-loud for placeholders that appear in live code: if a token has no value, the build stops and names the offending token rather than silently substituting an empty string.
Placeholders that occur only in POD or comments do not require values. --no-strict may be used to downgrade a missing live-code value to a warning and leave the placeholder unchanged.
Keeping the build system up to date
The following targets manage the lifecycle of the build system itself:
make check-upgrade/make upgrade-check-
Checks MetaCPAN to see if a newer version of
CPAN::Maker::Bootstrapperis available. make publish-
Builds the distribution tarball, unpacks it into a temporary directory, runs its normal
Makefile.PL, build, and test sequence, and uploads the tarball to PAUSE if all checks succeed.Set the PAUSE credentials with:
make publish PAUSE_USER=username PAUSE_PASSWORD=password make upgrade-
Checks MetaCPAN, installs the latest version via
cpanm, then automatically runsmake updateto refresh the managed project files. make update-
Copies the managed files from the currently installed bootstrapper distribution into your project directory. After running, use
git diffto review what changed.The following files are managed and may be updated:
Makefile .includes/bootstrap.mk .includes/perl.mk .includes/local.mk .includes/git.mk .includes/help.mk .includes/update.mk .includes/upgrade.mk .includes/version.mk .includes/release-notes.mk .includes/bash-completion.mk .includes/modulino.mk .includes/publish.mkYour project.mk, buildspec.yml, requires, VERSION, source files and tests are never touched by
make update. make cpanm-
Installs
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 managed files directly. Use config.mk for documented build variables and project.mk for project-specific targets, rules, and build ordering.
The Makefile itself is also managed and will be overwritten by make update. Use the documented project-level configuration and extension files instead.
Dependencies Management
The build system scans .pm.in and .pl.in source files and maintains the dependency files used by CPAN::Maker when generating distribution metadata:
F<requires>
F<test-requires>
F<recommends>
F<suggests>
Distribution dependency scanning is controlled by SCAN. Set SCAN=OFF to skip updates to requires, test-requires, recommends, and suggests for a build; the default is 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.
The local dependency library
Syntax checking is performed in a hermetic environment: each generated .pm is compiled with perl -wc against lib and a project-local library at local/lib/perl5 only, with PERL5LIB explicitly cleared for the check. This ensures a module's dependencies are actually declared and installed, rather than being satisfied by chance from whatever happens to be in your PERL5LIB or system @INC. A dependency that compiles on your machine but is missing from the declared dependency set will fail the build here instead of surprising you on a clean install or CI box.
To populate that library, the build installs your declared dependencies into local/ using cpm (preferred) or carton:
make local # installs requires/recommends/suggests/test-requires into local/lib/perl5
This runs automatically as a prerequisite of the module build, so a normal make installs dependencies first, then syntax-checks against them.
cpm is preferred because it supports multiple resolvers directly from build-mirrors. carton is also supported; see "build-mirrors" for its mirror behavior.
The use of a build-mirrors file versus specifying the mirrors in the dependency files determines the scope of their use. build-mirrors (and cpanfile mirror declarations) set the resolvers used for every dependency in the build. To route a single module to a specific mirror, URL, or distribution -- without affecting how anything else resolves -- annotate that module's entry in requires with mirror=, url=, or dist= (see "build-mirrors").
The + prefix has a second use beyond protecting mirror annotations. Because the check is hermetic, a module reached only at runtime will not be found unless it is declared. Static scanning (scandeps-static.pl) cannot see a dependency loaded dynamically -- for example a module pulled in through a method call rather than a use statement -- so it will never add it to requires. Declare such modules explicitly with +, which makes the entry sticky and survives every rescan:
+Log::Log4perl # loaded at runtime via a framework call; scanner can't see it
build-mirrors
When using the preferred CPAN installer (cpm), the build system reads mirror URLs, one per line, from a build-mirrors file in the project root and passes each as a resolver. This allows the build to resolve dependencies against one or more configured CPAN-compatible repositories, including private DarkPAN repositories.
When carton is used, because it does not support multiple mirrors when setting the mirror using an environment variable, the build system will use the first mirror in your build-mirrors file if present. carton supports multiple mirrors only by specifying them in the cpanfile.
Entries in requires, suggests, and recommends may carry optional qualifiers after the version to control where a module resolves from:
+Foo::Bar 1.0 mirror=https://cpan.openbedrock.net/orepan2
+Baz::Qux 2.5 url=https://example.com/authors/id/D/DU/DUMMY/Baz-Qux-2.5.tar.gz
+Xyz::Abc 0.9 dist=Xyz-Distribution
Only dist=, url=, and mirror= are permitted after the version; anything else is an error. Prefix such entries with the sticky + so the scanner does not strip the annotation on a later rescan. See CPAN::Maker for the full format.
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 wrapper script for invoking 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
CPAN::Maker::Bootstrapper provides a clean-room build path that can be used locally or from a CI system.
The CI design separates source acquisition from project build responsibilities:
source acquisition caller or CI system
build environment builder
project build make
A CI system such as GitHub Actions is responsible for checking out the project. builder then operates on that existing project directory, installs the required build environment and dependencies, and runs the project build.
make build-ci provides the corresponding local clean-room build. It uses the current working tree as its source, copies that tree into a disposable container build directory, and invokes builder there.
This separation keeps builder independent of repository hosting, branch selection, and source-control workflow while allowing the same build mechanism to be used both locally and in CI.
The build lifecycle is:
builder.env
|
v
builder-pre
|
v
make
|
v
builder-post
builder can run unmodified in GitHub Actions, in another CI runner, or by hand from the command line.
Running builder manually
builder operates on an existing project directory. Source acquisition is deliberately outside its responsibility; the caller must clone, check out, or otherwise provide the project before invoking builder.
Run it from the root of a project:
./builder
or pass the project directory explicitly:
/builder /path/to/project
The project directory defaults to the current working directory.
builder changes to that directory, installs the build environment and declared dependencies, loads the project CI environment, and runs the configured build lifecycle.
For the standard containerized clean-room build, use:
make build-ci
make build-ci copies the current working tree into a disposable build environment and invokes builder there. Because it operates on the current working tree rather than cloning the repository, the build may include uncommitted and untracked files present on disk.
Environment variables
builder accepts environment variables that control dependency installation and build behavior.
INSTALLER-
The command used to install Perl dependencies.
The default is:
cpm install -g --show-build-log-on-failure --verbose NO_ECHO-
Passed through to the generated Makefile when set.
CMB_VERSION_DRIFT-
Controls how builder handles differences between the installed CPAN::Maker::Bootstrapper version and the version expected by the project.
The generated builder.env defaults this to:
CMB_VERSION_DRIFT=ignore
builder.env
Before running the project build, builder loads builder.env from the project root when that file exists.
Variables defined there are exported to the build environment.
A generated project includes:
CMB_VERSION_DRIFT=ignore
NO_ECHO=
builder.env provides a project-local place to customize CI build behavior without modifying builder itself.
Builder lifecycle hooks
builder exposes two Makefile hooks around the main project build:
builder-pre
builder-post
The build lifecycle is:
builder.env
|
builder-pre
|
make
|
builder-post
builder-pre runs after builder.env has been loaded and before the main make invocation.
builder-post runs only after the main build completes successfully.
Generated projects define both targets as empty double-colon targets:
builder-pre::
builder-post::
Projects may extend them in project.mk without modifying the managed Makefile.
For example:
builder-pre::
./prepare-ci-environment
builder-post::
./collect-build-artifacts
These hooks are intended for project-specific CI setup and post-build work that should remain outside the managed build files.
make build-ci
make build-ci runs the current project in a disposable containerized build environment.
Unlike a CI workflow that clones the repository, build-ci uses the current working tree as its source. The project is mounted read-only, copied into the container build area, and then passed to builder.
This means the build reflects the files currently present on disk, including uncommitted changes and untracked files.
The source tree itself is not modified by the container build.
make build-ci accepts the following variables:
DOCKER_BUILD_IMAGE - container image used for the build
DOCKER_CPAN_INSTALLER
- dependency installer command used in the container
BUILD_LOG - path used for captured build output
MODULE_NAME - primary module name passed into the clean-room build
For example:
make build-ci
or:
make build-ci DOCKER_BUILD_IMAGE=debian:trixie
The command exits with the status of the container build even though the output is also written to BUILD_LOG.
Builder input files
builder recognizes project files that supply additional build requirements without modifying builder itself.
- build-apt-deps
-
A whitespace-separated list of additional Debian packages required by the project build.
builder installs these packages in addition to its standard build environment before installing Perl dependencies.
For example:
libxml2-dev libpq-dev - build-mirrors
-
A list of CPAN mirror URLs, one per line.
Use this file when the build requires a DarkPAN or another additional CPAN-compatible repository.
For example:
https://cpan.openbedrock.net https://cpan.metacpan.orgThe configured mirrors are used when resolving project dependencies.
These files describe build inputs. For environment variables use builder.env; for project-specific Makefile behavior use project.mk.
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 MetaCPANcpmorcarton- installs declared dependencies into a project-local library (local/lib/perl5) for hermetic syntax checking
You can set make variables like SYNTAX_CHECKING in config.mk, which is included on every invocation of make, to alter build behavior -- for example SYNTAX_CHECKING=OFF to skip the check when neither installer is present (undeclared dependencies then go undetected).
Note: neither cpm nor carton is a hard prerequisite of CPAN::Maker::Bootstrapper itself; they are needed only to populate the local library for hermetic checking. cpm is preferred for its multi-mirror support (see "build-mirrors").
CAVEATS
- .pm and .pl Generation
-
Generated .pm and .pl files are derived from their .pm.in/.pl.in sources through
cmb resolve-varsand are read-only. Always edit the .in source.Use
@PACKAGE_VERSION@like this:our $VERSION ='@PACKAGE_VERSION@'; - The import feature cannot be used with
--stub - git
-
gitis used throughout the framework.make gitinitializes the repository and creates the initial commit, and the bootstrapper reads user identity and related defaults from .gitconfig when no separate configuration file is supplied.
FAQ
My build is failing with a module not found error during syntax checking
There are several common causes.
One possible cause is an inter-module build-order dependency. The build system normally detects dependencies between modules in the distribution and writes them to deps.mk, allowing make to build prerequisite modules before syntax-checking modules that depend on them.
For example, if lib/Foo/Bar.pm uses lib/Foo.pm, the generated dependency rules ensure that Foo.pm is built first.
If the dependency cannot be inferred automatically, declare it explicitly in project.mk:
lib/Foo/Bar.pm: lib/Foo.pm
See "Inter-module dependencies" for details.
Another cause is a real dependency that is not installed in local/. Because syntax checking runs against local/lib/perl5 with PERL5LIB cleared, a dependency that is present elsewhere on your system but not declared will fail here.
Confirm it is in requires (add it with a sticky + if the scanner can't see it -- see "Dependencies Management"), then make local to install it. This is the check working as intended: it catches a missing declaration on your machine instead of on someone else's.
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
Files listed in PERLWC_SKIP are excluded from the perl -wc syntax-checking and POD-checking stages. They are still built and included in the distribution; only those validation steps are skipped.
How do I do a fast build during development?
make quick
This disables distribution dependency scanning and all linting (perltidy, perlcritic) for the current build. requires, test-requires, recommends, and suggests are not updated. Syntax checking remains enabled.
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 distribution 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.
The build verifies that files listed in extra-files are tracked by git. This helps catch files that have been added to the distribution but accidentally omitted from the project repository.
Some extra files are generated build artifacts and therefore should not be committed to the repository. Add those files to extra-files.skip, one file per line:
generated/service-data.dat
share/generated-index.json
Blank lines and lines beginning with # are ignored.
extra-files.skip only disables the git tracking check for those files. The files remain part of the distribution and continue to be included as dependencies when determining whether the distribution tarball must be rebuilt.
When DARKPAN_REQUIRES is enabled, cpanfile.darkpan and cpanm.darkpan are automatically added to buildspec.yml as extra files. They are therefore subject to the normal git tracking check. If the developer wants to include these generated manifests in the distribution without tracking them in the repository, they may be added explicitly to extra-files.skip. See "DARKPAN_REQUIRES".
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 Management" 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 when its prerequisites require regeneration 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".
make update overwrote something I changed in a managed file
The managed files in .includes/ should never be edited directly. Use config.mk, project.mk, or the other documented project-level extension points instead.
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 dependency scanning temporarily?
make SCAN=OFF
This skips distribution dependency scanning for that run, so requires, test-requires, recommends, and suggests are not updated.
Inter-module dependency discovery for deps.mk is independent of SCAN and may still run when syntax checking is enabled. The default is SCAN=ON.
How do I disable syntax checking temporarily?
make SYNTAX_CHECKING=OFF
Similarly you can disable individual quality gates:
make PERLTIDY="" PERLCRITIC=""
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.
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 files
release-<version>.lst - a list of added, modified, and removed files
release-<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. The POD make variable controls that behavior:
make POD=extract-
extractwill strip POD from your module and create a.podfile containing the stripped POD that will be added to your distribution. make POD=remove-
removewill strip POD from your module. No POD will be included in the distribution.
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-relatedYour buildspec.yml and project.mk if 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 scanner used for CPAN dependency discovery and for inter-module build-order discovery through deps.mk.
DEPENDENCIES
The current runtime, build, test, recommended, and suggested dependencies are declared in the distribution metadata generated by CPAN::Maker.
See Makefile.PL, META.json, or META.yml in the distribution for the authoritative dependency set.
Some optional features require additional dependencies only when those features are used.
VERSION
This documentation refers to version 2.4.0
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.