NAME

CPAN::Maker::Bootstrapper - Scaffold a new CPAN distribution in one command

SYNOPSIS

# Create a configuration file (recommended first-time setup)
cmb create-config > ~/.cpan-makerrc
export CPAN_MAKER_CONFIG=$HOME/.cpan-makerrc

# Create a new plain Perl module project
cmb --module  My::New::Module

# Create a CLI module project (inherits from CLI::Simple)
cmb --module My::New::CLI --stub cli

# Use a custom stub
cmb --module My::Module --stub /path/to/mystub.pm

# Import files from another project
cmb --module My::Module \
 -I /path/to/my-module/lib -I /path/to/my-module/bin \
 --installdir /tmp/My-Module

# Install into a specific directory
cmb --module My::Module --installdir ~/git/My-Module

# Override git identity
cmb --module My::Module --username "Rob Lauer" --email rob@example.org

# Run a code review on a module (set API key in environment)
export LLM_API_KEY=$(cat ~/.ssh/anthropic-api-key)
cmb code-review lib/My/Module.pm    

DESCRIPTION

CPAN::Maker::Bootstrapper

CPAN::Maker::Bootstrapper scaffolds a new Perl project that leverages CPAN::Maker to create a ready-built CPAN distribution. It installs a project Makefile, a buildspec.yml pre-populated and ready to feed to CPAN::Maker, optional stub source file and test file, and supporting makefile snippets that implement the bootstrapper framework. It then runs make to generate your distribution tarball.

The result is a project that can produce a distributable tarball with a single make invocation. As you iterate on your application, new dependencies are discovered and added to the build system automatically.

CPAN::Maker::Bootstrapper also provides AI-assisted development tools via the Anthropic Claude API. These include iterative code review with structured finding annotations, POD documentation review, optional POD generation, and AI-generated release notes. See "LLM Commands" and "THE REVIEW WORKFLOW" for details.

NOTE: Check out the release-notes directory in the GitHub project for examples of release notes generated by the LLM.

QUICK START

Install the bootstrapper and its dependencies:

cpanm CPAN::Maker::Bootstrapper

Note: Before scaffolding your first project, consider running create-config to set up a personal configuration file - it pre-populates your git identity, GitHub username, and preferred project directory so you never have to pass them on the command line. See "CONFIGURATION" for details.

Scaffold a new project:

cmb --module My::Module --installdir ~/git/My-Module

The bootstrapper creates the project directory, installs the build system, generates stub source and test files, and runs make automatically. By the time it finishes you already have a working distribution tarball in ~/git/My-Module.

By default the final build step applies full linting: syntax checking (perl -wc), perltidy conformance, and perlcritic at its default severity (5 - the most severe violations only). If your stub or imported source isn't tidy, has a severity-5 perlcritic violation, or fails to compile, the build - and therefore install - will fail. Disable these gates with environment variables if you want to bootstrap first and clean up after:

make LINT=off SYNTAX_CHECKING=off SKIP_TESTS=1

LINT disables perltidy and perlcritic; SYNTAX_CHECKING disables the perl -wc check -- both are interpreted by the build system installed by this module. SKIP_TESTS is interpreted by CPAN::Maker to skip running the test suite when building the distribution tarball.

Have an existing project?

cmb -I lib -I bin --module Foo::Bar

Next Steps

WHY YOU SHOULD CONSIDER USING CPAN::Maker::Bootstrapper

If you have ever reached for Jenkins, GitHub Actions, CircleCI, or a sprawling shell script to automate your Perl builds, consider what those tools actually require: a server or cloud account, a proprietary YAML DSL, plugin ecosystems with their own release cycles, containers, agents, and configuration files that only run in one specific environment.

The CPAN::Maker build system runs everywhere Perl runs - your laptop, a remote EC2 instance, a colleague's workstation - with no setup beyond cpanm CPAN::Maker::Bootstrapper. git clone && make is always sufficient to build a fresh checkout.

The Stack

The build system is built on three tools that have been solving these problems correctly for decades:

Together they give you a complete, auditable, version-controlled build system that is trivially debuggable with make -n and bash -x, self-documents via make help, and needs no external services to run.

Best Practices Out of the Box

The installed build system encourages professional Perl development habits from the start:

Perl Quality Tools

The build system supports optional Perl quality gates controlled via your configuration file. Set the following keys in the [cpan-maker] section:

syntax-checking = on          # enables perl -wc on generated files
perltidyrc = ~/.perltidyrc    # enables perltidy stage gate
perlcriticrc = ~/.perlcriticrc # enables perlcritic stage gate

These can be overridden per-run from the command line:

make SYNTAX_CHECKING=off      # disable syntax checking
make PERLTIDYRC=""            # disable tidy gate
make PERLCRITICRC=""          # disable critic gate

Add modules that cannot be syntax-checked outside their runtime environment to PERLWC_SKIP in project.mk:

PERLWC_SKIP = bin/startup.pl

Add inter-module build dependencies to project.mk when modules depend on each other at build time:

lib/Foo/Bar.pm: lib/Foo.pm

To disable all linting at once:

make LINT=off

Or use make quick to disable both scanning and linting in one step.

A GNU Make Tutorial in Disguise

The .includes/ directory is also a practical demonstration of advanced GNU make techniques that most developers never encounter - working, production-tested examples you can learn from and adapt:

If GNU make is the cast-iron pan of build tools - virtually indestructible, infinitely useful, and unfairly overlooked in favor of shinier alternatives - then CPAN::Maker::Bootstrapper is the recipe book that shows you what it can really do.

IMPORTING FILES

The --import|-I option allows you to bring existing Perl source files into a new Bootstrapper project. This is the primary mechanism for migrating an existing project or consuming a scaffold tarball generated by cli-simple -scaffold.

The --import option may be specified multiple times to import from several directories in a single operation:

cmb --module My::Script \
  --import /path/to/roles \
  --import /path/to/bin \
  --installdir .

Determining the Primary Module

The bootstrapper determines the primary module name in the following order:

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

The importer recursively scans the path provided by --import and brings in the following file types:

All imported files receive the .in extension because they become source inputs to the build system. The build generates the final .pm, .pl, and script files from these sources, substituting version tokens and running syntax checks along the way.

Module Name Requirement

When using --import you must also specify --module with the primary module name of the distribution. The importer cannot infer the module name from the imported files alone:

cmb --module My::Script --import /path/to/source --installdir .

The Build After Import

After creating the project source tree the importer runs make with linting disabled but syntax checking and dependency scanning enabled:

make LINT=off SYNTAX_CHECKING=on SCAN=on

This serves two purposes - it validates that the imported files are syntactically correct Perl, and it runs scandeps-static.pl against the source to seed the requires and test-requires dependency files.

The build will attempt to produce a distribution tarball. If the build fails, make.log and make.err are written to your current working directory for diagnosis.

Next Steps After a Successful Import

After a successful build you have a complete, buildable CPAN distribution, although it may not reflect everything you need for your project. Typical next steps:

Limitations

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 your global .gitconfig file or a properly formatted .ini file to populate some of the options used when creating a distribution and using the AI commands. If you have a GitHub user account add your username:

git config --global user.github <your-username>

If you typically create projects in one directory, add the basedir option:

git config --global cpan-maker.basedir $HOME/git

If you want to create a different configuration file it should have at least the following entries:

[user]
       email = your-email@somedomain
       name = First Last
       # use to construct GitHub resource URLs
       github = github-user

[cpan-maker]
       basedir   = /home/myhome/git
       # indicates the resources section of Makefile.PL should contain github references
       resources = github
       llm-api-key-helper = cat ~/.ssh/anthropic-api-key

Environment

INSTALLED PROJECT FILES

The following files are installed into the project directory:

THE PROJECT MAKEFILE

The installed Makefile is self-configuring. It can derive everything from MODULE_NAME or the package name inside a custom stub file.

MODULE_PATH  - lib/My/New/Module.pm (from MODULE_NAME)
PROJECT_NAME - My-New-Module (from MODULE_NAME)
TARBALL      - My-New-Module-1.0.0.tar.gz (from PROJECT_NAME + VERSION)

If MODULE_NAME is not supplied on the command line, it is inferred from the project directory name.

Key Makefile targets:

README.md

The Makefile will automatically create a README.md from your Perl module's pod. The stock buildspec.yml will include that README.md in the distribution's share directory. If you want the README.md to be included in the distribution but not installed, edit the buildspec.yml file.

Before

extra-files:
  - ChangeLog
  - share:
    - README.md

After extra-files: - ChangeLog - README.md

If you want a different README.md generated create a README.md.in file. That file will be filtered through md-utils.pl (from Markdown::Render) to produce a .md file.

COMMANDS

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.

OPTIONS

THE REVIEW WORKFLOW

CPAN::Maker::Bootstrapper allow you implement a structured iterative code review workflow built around JSON review files and developer-applied disposition annotations. The workflow converges over several rounds, with each round potentially costing less as noise is suppressed and findings are resolved.

Overview

Each review round consists of three steps:

Dry Run Mode

Before your prompt and code are submitted for review, the script will output a table of showing you the estimated cosst based on token counts. The input token count is derived by calling the "COUNT TOKEN" endpoint API with the message to be submitted for review. The input token count is therefore accurate, while the output token count is an estimate.

To stop the script for actually submitting the message for review, use the --dry-run option. This will abort the process immediately prior to submission.

Dispositions

Each finding in the annotations file must be given one of the dispositions describe below before the next review can be submitted. The prompt sent to the LLM is designed around these dispositions. This helps the LLM produce findings that hopefully will converge on a clean review.

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:

When all findings have dispositions and no new substantive issues appear, the code is ready to ship.

The Release Artifact

When you are satisfied with the review state, finalize it with --finalize-annotations:

cmb annotate --finalize-annotations -a 1:wrong -a 2:reject lib/My/Module.pm

This applies any remaining dispositions, validates that all findings are annotated, reads the version from the VERSION file, and writes the versioned release artifact:

CPAN-Maker-Bootstrapper-1.1.0-REVIEW.json

This file serves as a code review certification for the release - a machine-readable record of every finding examined, every logic confirmation made, and every disposition applied before the version was published. Commit it to the repository alongside your ChangeLog.

All findings marked WRONG are automatically converted to WRONG-RECONSIDER in the release artifact, prompting careful re-examination on the first review of the next version rather than permanent suppression.

Cost Management

Typical review costs run $0.05-0.10 per run on a moderately sized module with POD stripped depending on the model you choose. The default model used for POD review is claude-haiku-4-5-20251001 and claude-sonnet-4-6 for code review. Costs decrease over successive rounds as the model spends fewer output tokens re-explaining suppressed findings.

Use your own prompt profiles (--prompt-profile) to suppress entire classes of noise before they reach the annotation file. A well-tuned profile for your application type is the highest-leverage cost reduction available.

See Also

"LLM Commands", "PROMPT PROFILES", CPAN::Maker::ConfigReader

PROMPT PROFILES

Prompt profiles are additive prompt fragments that customize the review behavior for specific application types. They are appended to the base review prompt before submission and are intended to focus the review on relevant concerns while suppressing noise that does not apply to the target context.

NOTE: Prompts count toward your input token count. Be succinct and accurate.

Using Profiles

Pass one or more profiles using the --prompt-profile option:

cmb code-review --prompt-profile cli-tool MyModule.pm

Multiple profiles may be combined:

cmb code-review --prompt-profile cli-tool --prompt-profile security MyModule.pm

Profiles are resolved from the .prompts/ directory in the current project. A profile named cli-tool resolves to .prompts/cli-tool.prompt. Add your own prompt profiles and commit them to your project.

Built-in Profiles

The following profile is installed with the distribution:

Creating Custom Profiles

A profile is a plain text file in .prompts/ containing additional prompt instructions, one per line. Lines beginning with # are treated as comments and stripped before submission. Profile instructions use the same format as the base review prompt.

Example .prompts/security.prompt:

# security profile - add to any review where input handling matters
- Treat all caller-supplied input as untrusted regardless of source.
- Flag any use of eval, system, or exec that incorporates external data.
- Flag missing taint checks on data used in file or system operations.

Planned Profiles

The following profiles are planned for future releases:

Community contributions of additional profiles are welcome. See https://github.com/rlauer6/CPAN-Maker-Bootstrapper/issues.

EXTENDING THE BUILD SYSTEM

The bootstrapper's Makefile is intended to be immutable and work across all of the projects that use CPAN::Maker::Bootstrapper. Our goal is to keep Makefile working for you even when we make updates to the bootstrapper.

However, you own Makefile and are free to do with it as you please. But we strongly advise that you read the sections below and follow the recipe as the saying goes, to use and update the build system as it was intended.

The installed Makefile is a managed file - it can be updated by using the make target update when a new version of CPAN::Maker::Bootstrapper is released.

make update

You are strongly advised not to modify the Makefile - your changes will be overwritten if you run make update.

Instead, the recommended workflow, should you need to add new make targets or control the order of the build based on dependencies is to add those to project.mk. All managed build system files live in the .includes/ directory where they are write-protected and clearly separated from your project files. The Makefile includes them automatically and conditionally includes project.mk from the project root:

-include config.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
-include project.mk
-include extra-files.mk

project.mk remains in the project root - it is your file, always writable, and never touched by make update. The leading - on its include means make will not complain if it does not exist yet. This gives you a sanctioned, upgrade-safe extension point for anything project-specific.

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 build identically -- there is no per-project drift hiding in a locally-edited 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:

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 on every build, 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:

Key variables you can override on the make command line or in project.mk:

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

What does NOT belong in project.mk

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 via the .vars sidecar. 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 on every build. The token grammar is uppercase-only (@[A-Z0-9_]+@), so placeholders never collide with real Perl such as @_ or @ISA.

The substitution is fail-loud: if a source contains a @TOKEN@ that is not resolved -- because it was never declared, or declared but left empty -- the build stops and names the offending token, rather than silently substituting an empty string and producing a subtly broken module. This is what makes custom tokens safe to rely on: a missing value is a build error, not a runtime surprise.

Keeping the build system up to date

The following targets manage the lifecycle of the build system itself:

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:

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:

What You Should Never Modify

The files in .includes/ - perl.mk, git.mk, help.mk etc. - are managed files that will be overwritten by make update. Do not modify them directly. If you need to override behavior they provide, do so in project.mk using Make's double-colon rule pattern or by setting variables before the include.

The Makefile itself is also managed and will be overwritten by make update. Your extension point is exclusively project.mk.

Dependencies Management

The Makefile will attempt to detect Perl module dependencies by scanning .pm.in and .pl.in files and creating the requires and test-requires files whenever you run make. These files are used by the make-cpan-dist.pl utility to specify the dependencies in your CPAN distribution file. You can prevent that by setting the environment variable SCAN=OFF. The default is SCAN=ON.

To prevent an entry from being removed by a rescan, prefix the module name with +. These entries are sticky and survive all subsequent scans even if the scanner no longer detects them. To pin a specific version, simply edit the version number in the requires file. If the scanner subsequently detects a different version, the Makefile will preserve your pinned version. Note that pinned versions are never updated automatically - if you want to adopt a newer version you must edit the file manually.

In your requires file:

+Foo::Bar 1.0    # sticky - survives all rescans
Baz::Qux  2.5   # version pinned - scanner won't override this version

Note: These two mechanisms are independent - + controls whether an entry survives rescans, while the version number controls what version is required.

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 requires 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 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 mirrors (see build-mirrors); carton is supported but resolves against a single mirror -- its default, or the one named in PERL_CARTON_MIRROR. Multiple mirrors may be specified in the cpanfile for both carton and cpm.

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 lets the build resolve dependencies from a private DarkPAN in addition to (or instead of) the public CPAN. If build-mirrors is absent, the installer uses its default mirror.

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 bash wrapper script that invokes your module. By default it uses MODULE_NAME, producing a script named after the module:

make modulino

For a project named Foo::Bar this creates bin/foo-bar.in. make then builds bin/foo-bar from that source file via a pattern rule, and the executable ends up in the distribution.

To create a modulino wrapper for a module other than the primary project module, override MODULE_NAME:

make modulino MODULE_NAME=Foo::Bar::Buz

This creates bin/foo-bar-buz.in invoking Foo::Bar::Buz.

To give the wrapper a short or memorable name independent of the module name, set ALIAS:

make modulino MODULE_NAME=Foo::Bar::Buz ALIAS=fbb

This creates bin/fbb.in which still invokes Foo::Bar::Buz. ALIAS accepts either a plain name (fbb) or a module-style name (Foo::Bar::Buz) - colons are converted to hyphens and the result is lowercased.

The generated wrapper scripts (without the .in suffix) are automatically added to .gitignore since they are build artifacts. The .in source files are tracked by git.

Continuous Integration

make workflow installs builder, a self-contained bash script that performs a clean-room build: it installs a minimal Perl toolchain, installs your distribution's dependencies, and runs make. It's designed to run unmodified in GitHub Actions, in any other CI runner, or by hand from the command line.

Running builder manually

builder can build any git repository, not just the one it's installed in - useful for testing a build in isolation without touching your working tree:

docker run --rm -v "$(pwd)/builder:/builder:ro" \
   -e BUILD_BRANCH=$(git branch --show-current) \
   debian:trixie \
   bash /builder https://github.com/your-user/Your-Module.git

Note: a repository URL is currently required - builder does not yet support building an already-checked-out project mounted directly into the container.

Environment variables

Override files

Commit these to your project root to customize a CI build without touching builder itself:

make build-ci variables

make build-ci wraps the manual docker invocation above. It accepts:

DOCKER_BUILD_IMAGE  - container image to build in (default: debian:trixie)
BRANCH              - branch to build (default: current branch)
INSTALLER           - see L</Environment variables> above (default: cpm)
BUILD_LOG           - path to write build output (default: timestamped)

See Also

"make workflow", "make build-ci"

PREREQUISITES

The following tool(s) must be on your PATH:

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

FAQ

My build is failing with a module not found error during syntax

checking

This is almost always a build-time dependency ordering issue. If lib/Foo/Bar.pm uses lib/Foo.pm, make may attempt to build and syntax-check Foo/Bar.pm before Foo.pm exists. Declare the dependency in project.mk:

lib/Foo/Bar.pm: lib/Foo.pm

This tells make to build Foo.pm first. See "Inter-module dependencies" for details.

A third cause, new to the hermetic build: the module is a real dependency that simply isn't 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

How do I do a fast build during development?

make quick

This disables dependency scanning and all linting (syntax checking, perltidy, perlcritic) for the current build. Your requires and test-requires files are not updated and no quality gates run.

Use make without flags when you are ready to do a full build before committing or releasing.

You can also disable individual features:

make SCAN=off          # skip dependency scanning only
make LINT=off          # skip all linting only
make SYNTAX_CHECKING=off  # skip syntax checking only

How do I add a new module or script to the project?

Create the source file with the .pm.in or .pl.in extension in the appropriate directory:

lib/My/New/Module.pm.in
bin/my-script.pl.in

The build system discovers them automatically via find-files - no changes to the Makefile are required. The next make will include them in the dependency scan and the distribution.

How do I include additional files in the distribution?

Edit buildspec.yml and add entries to the extra-files section:

extra-files:
  - ChangeLog
  - README.md
  - share:
    - my-config-template.yml
    - my-data-file.json

Files listed under share: are installed into the distribution's share directory and can be accessed at runtime via File::ShareDir.

I want to pin a version or add a module the scanner missed

Edit requires directly. Prefix the module name with + to make the entry sticky - it will survive all subsequent rescans even if the scanner no longer detects it:

+My::Required::Module 1.5

To pin a version without making the entry sticky, just set the version number. The scanner will preserve your version if it detects a different one on subsequent builds:

Some::Module 2.0

These two mechanisms are independent - + controls survivability, the version number controls what version is required. See "Dependencies" for full details.

I want to exclude a module the scanner found

Create a requires.skip file in the project root with one module name per line:

My::Own::Module
Some::Transitive::Dep

The scanner will never add these to requires. Use test-requires.skip for the same effect on test dependencies.

Note that on a clean first build neither skip file has any effect since there is no prior requires file to compare against. The skip list takes effect from the second build onward.

I edited a .pm file and my changes disappeared

The .pm files in lib/ are generated from the .pm.in sources and are write-protected. Always edit the .pm.in file - the .pm is regenerated on every make and your changes will be lost.

If you are unsure which file to edit:

ls -l lib/My/Module.pm lib/My/Module.pm.in

The .pm.in file is the one you own.

Why does my build say it has drifted from the installed bootstrapper?

This means your project's managed files (Makefile, .includes/*.mk) no longer match what your currently installed CPAN::Maker::Bootstrapper would generate. There are two ways this happens - upgrading your bootstrapper (e.g. via cpanm \--upgrade-all) instantly "drifts" every project you haven't yet updated, or a managed file was edited by hand. Both are fixed the same way:

make update

If you don't want a drifted project to fail the build outright, set CMB_VERSION_DRIFT=warn (or =ignore) in that project's config.mk. See "Automatic Drift and Update Checks". =head2 make update overwrote something I changed in a managed file

The managed files in .includes/ should never be edited directly - that is what project.mk is for. However if you did modify a managed file and make update overwrote it, git has you covered:

git diff .includes/perl.mk
git checkout .includes/perl.mk

This is why make git and committing your .includes/ directory is strongly recommended - git is your safety net for the entire build system.

make says nothing to do but my source changed

The most common cause is that the generated .pm file is newer than the .pm.in source. This can happen if you accidentally edited the .pm directly or if file timestamps got out of sync. Force a rebuild:

touch lib/My/Module.pm.in

Or do a clean rebuild:

make clean && make

How do I disable scanning temporarily?

make SCAN=off

This skips the dependency scan entirely for that run - useful when you have many modules and want a fast build during active development. The default is SCAN=ON.

How do I disable syntax checking temporarily?

make SYNTAX_CHECKING=off

Similarly you can disable individual quality gates:

make PERLTIDYRC="" PERLCRITICRC=""

How do I upgrade the build system?

make upgrade

This checks MetaCPAN for a newer version of CPAN::Maker::Bootstrapper, installs it via cpanm, and automatically refreshes the managed files in .includes/ with make update. Review the changes with git diff and revert anything you don't want with git checkout.

If cpanm is not installed:

make cpanm && make upgrade

I want to add a bash script to my distribution

Create the script in bin/ with a .sh.in extension:

bin/my-script.sh.in

The build system will process it through the standard token substitution (replacing @PACKAGE_VERSION@ and @MODULE_NAME@), make it executable, and include it in the distribution automatically.

If your script is more than a few lines of bash, consider writing it as a modulino instead - a Perl module that doubles as a runnable script. Modulinos are easier to test, encourage encapsulation, and give you the full power of Perl and CPAN. The build system has first-class support for them:

make modulino

This generates a bash wrapper in bin/ that invokes your module as a script if it uses the modulino pattern:

caller or __PACKAGE__->main;

See "MODULINOS" for full details.

What is make release-notes used for?

make release-notes generates three artifacts comparing the current working state of your repository against the previous git tag:

These are primarily useful for generating release notes and changelogs, and for submitting targeted patches. Run it after bumping the version with make release, make minor, or make major and before publishing to CPAN:

make minor
make release-notes
# review release-1.1.0.diffs
make

The artifacts are all the clues needed for LLMs to produce accurate and well written release notes for your project.

The release artifacts are cleaned up by make clean.

Can I distribute the POD in my modules separately?

When you package your CPAN distribution you can strip the pod from your modules or you can extract the pod and provide them as separate .pod files. There are two make environment variables you can set to control that behavior.

The dependency resolver keeps adding a file I don't want to

list. How can I tell it to skip those files?

Add a requires.skip file to exclude modules from the scanned list. Sometimes the scanner may include modules that are optional or modules you just don't want to include as requirements because they are already included in a module you have already required.

Similarly, test-requires.skip excludes modules from the test dependency scan.

On a clean first run neither requires nor test-requires exists yet, so the raw scanner output becomes the dependency file - meaning skip list and pins have no effect until the second run.

Something still doesn't work - how do I report an issue?

First check the "FAQ" sections above - your issue may already be covered.

If you believe you have found a bug or want to request a feature, please open an issue on GitHub:

https://github.com/rlauer6/CPAN-Maker-Bootstrapper/issues

When reporting a bug please include:

Pull requests are welcome. The project follows the standard GitHub fork-and-PR workflow.

SEE ALSO

CPAN::Maker - the distribution builder driven by buildspec.yml (includes make-cpan-dist.pl)

CLI::Simple - the CLI framework used by the bootstrapper itself and optionally by generated CLI module stubs

CPAN::Maker::ConfigReader - the git config reader bundled with this distribution, available for use in your own tools.

LLM::API - client interface to Anthropic's Claude API

Module::ScanDeps::Static - the static dependency scanner used by make requires and make test-requires to analyze your source files

DEPENDENCIES

CLI::Simple::Constants
CLI::Simple::Utils
CPAN::Maker::ConfigReader
Cwd
English
Email::Valid
File::Basename
File::Copy
File::Find
File::Path
File::ShareDir
File::Temp
JSON::PP
List::Util
Module::Metadata;

Required for AI Commands

Archive::Tar
Pod::Extract (required for code-review command)
Text::ASCIITable;

Recommend Packages

Term::ANSIColor

VERSION

This documentation refers to version 2.3.2

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.