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

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:

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

For projects that require explicit control over which files are imported and their destinations, use an import manifest:

cmb import import.yml

The manifest defines the source directory, primary module, and file-selection rules. Use --dry-run to preview the import plan before creating the project:

cmb --dry-run import import.yml

See "IMPORTING FILES" for automatic discovery and manifest-driven imports.

NEXT STEPS

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:

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:

Perl Quality Tools

The build system supports syntax checking, perltidy, and perlcritic as build-time quality gates.

Linting

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

PERLTIDYRC and PERLCRITICRC are initialized in the autogenerated build-config.mk file. During generation, Bootstrapper searches for the corresponding configuration files in the following order:

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.

NOTE: For reproducible builds, keep your lint configuration files in the project. Relying on configuration files in individual developers' home directories may produce inconsistent results across development environments and CI.

Syntax Checking

Syntax checking is controlled independently with SYNTAX_CHECKING:

make SYNTAX_CHECKING=OFF

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

Skipping Quality Gates

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

To establish consistent quality gate settings across development environments and CI, define them in config.mk and commit that file to source control. See "THE PROJECT MAKEFILE".

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:

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 system 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:

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.

Manifest-Driven Imports

The import command provides an alternative to automatic file discovery when importing an existing Perl project.

cmb import import.yml

An import manifest explicitly identifies the source directory, primary module, and files to import. It also allows source files to be mapped to specific destinations in the generated project.

For example:

base-dir: /path/to/existing/project
module: Foo::Bar

import:
  - lib:
      path: lib
      recurse: true
      ext: pm
  - bin:
      path: bin
      recurse: true
  - t:
      path: t
      recurse: true

The following top-level keys are required:

Each import specification supports the following keys:

Each entry in import must specify exactly one destination.

Use --dry-run to preview the file mappings before generating the project:

cmb --dry-run import import.yml

The import command uses the same underlying project construction mechanism as install, but obtains its file-selection plan from the manifest rather than automatic discovery.

Syntax checking is disabled during the initial import build. This allows projects with dependencies or runtime environments that have not yet been configured to be imported before addressing those requirements.

The --import option remains available for automatic discovery without a manifest.

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:

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 initial import build enforces the following settings:

SCAN=on
SYNTAX_CHECKING=off
LINT=off
SKIP_TESTS=1

Dependency scanning remains enabled during the initial import build.

First Task After a Successful Import

After a successful import you either have a project tree built for you or a tarball if you used the --project-tarball option.

The project is now capable of producing a CPAN distribution with the following caveats:

So, your first task is to make sure the dependencies are installed properly and the distribution builds. That means fixing tests as necessary, specifying dependencies missed by the scanner, tidying files or resolving perlcritic findings. If you are not interested in linting yet, turn that off on your first make (make LINT=off). To turn linting off permanently add LINT=off to your config.mk file.

See "FAQ" for more troubleshooting information for broken builds.

Next Steps

Once your project produces a CPAN distribution and passes your tests there are some other actions you should consider.

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 reads configuration from an INI-format file. The configuration file may be selected explicitly with --config, specified through the CPAN_MAKER_CONFIG environment variable, or discovered automatically.

Configuration values can be used to set defaults when scaffolding distributions and to configure installed extensions.

See "new" in CPAN::Maker::Bootstrapper::ConfigReader for the configuration file discovery order.

When --installdir is not supplied when you bootstrap a project, 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.

If name or email is missing from the [user] section, Bootstrapper attempts to obtain the missing values from ~/.gitconfig.

You can configure your name, email address, and GitHub identity with:

git config --global user.name <your-full-name>
git config --global user.email <your-email>
git config --global user.github <your-github-identity>

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

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

A Bootstrapper 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

See CPAN::Maker::Bootstrapper::ConfigReader for a complete description of the configuration file.

Use the --config option to use your custom config.

You can generate a starter configuration with:

cmb create-config > ~/.cpan-makerrc

Then point cmb at it by setting the CPAN_MAKER_CONFIG environment variable in your shell profile:

export CPAN_MAKER_CONFIG=$HOME/.cpan-makerrc

Environment

INSTALLED PROJECT FILES

The following files are installed into the project directory:

THE PROJECT MAKEFILE

The Makefile serves as the orchestration layer for building and maintaining a bootstrapped Perl project. It coordinates the recipes required to generate source files, resolve dependencies, run tests, build distributions, and perform other development and release operations. The individual recipes are organized into Makefile fragments under .includes/, which are included by the main Makefile.

Before these recipes can run, Make needs to establish the project's configuration. On the initial invocation, Make creates config.mk if it does not exist, then invokes cmb create-build-config to generate build-config.mk. The generated file contains the resolved project settings and paths to the tools used by the build.

cmb create-build-config determines the primary module name from MODULE_NAME, if supplied, or derives it from the project directory name. It uses that name to construct values such as MODULE_PATH, PROJECT_NAME, and UNIT_TEST_NAME. It also resolves configured defaults and locates development tools such as Perl, Git, and the dependency installers.

For example, a project with MODULE_NAME=My::New::Module produces:

MODULE_NAME    := My::New::Module
MODULE_PATH    := lib/My/New/Module.pm
PROJECT_NAME   := My-New-Module
UNIT_TEST_NAME := t/00-my-new-module.t

build-config.mk is generated configuration derived from the current build environment. It provides reasonable defaults by discovering available tools, configuration files, and other settings needed by the build. It is regenerated when a fresh build environment is established and should not be edited or committed.

config.mk provides persistent, project-specific overrides for these discovered settings. Values defined in config.mk take precedence over the corresponding defaults in build-config.mk.

Because discovered settings may differ between development machines and CI environments, projects should record their intended build policy in config.mk and commit that file to source control.

Changes to config.mk cause build-config.mk to be regenerated so the build incorporates the updated settings.

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 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

OPTIONS

EXTENSIONS

CPAN::Maker::Bootstrapper supports optional extensions that introduce new cmb commands, command-line options, configuration settings, and Makefile targets.

Extensions allow developers to add specialized workflows to their projects while retaining the build and release infrastructure provided by Bootstrapper. Each project can install and configure only the capabilities it needs.

An extension is packaged as a Perl distribution named CPAN::Maker::Bootstrapper::Role::ExtensionName. It provides a role.yml manifest, one or more Perl roles, and optionally a role.mk file containing Makefile recipes.

Extensions must be installed before they can be added to a project.

Two extensions illustrate this approach:

Both extensions are distributed independently of Bootstrapper and can be installed and enabled on a per-project basis.

Installing an Extension

First, install the extension distribution using your preferred CPAN client. For example:

cpanm CPAN::Maker::Bootstrapper::Role::Example

Then, from the root of an existing Bootstrapper project, run:

make add-plugin PLUGIN=Example

The add-plugin target invokes cmb add-plugin, installs the extension's project files, and runs make update.

The extension is registered beneath:

.roles/CPAN-Maker-Bootstrapper-Role-Example/

The directory contains a copy of the extension's role.yml manifest and, when supplied, its role.mk file.

The extension's Perl modules remain installed in the Perl environment; they are not copied into the project.

If the extension is already registered, add-plugin refuses to overwrite it. To replace the project-local extension files, use:

make add-plugin PLUGIN=Example FORCE=1

The existing files are backed up during installation and restored if validation fails.

The project-local .roles/ directory should be committed to version control so that other developers receive the same extension registration when checking out the project.

Those developers must also install the corresponding Perl extension distribution.

Extension Components

An extension can provide two complementary forms of functionality: commands executed by cmb and Makefile targets executed by make.

role.yml

The role.yml manifest is required. It declares the Perl roles and commands supplied by the extension, along with any option specifications and default option values.

The manifest is loaded by CLI::Simple, which integrates the extension's commands and options with those provided by CPAN::Maker::Bootstrapper.

When an extension is added to a project, add-plugin verifies that role.yml exists and that its declared roles can be loaded.

Perl Roles

Extension functionality is implemented in Perl roles composed using Role::Tiny.

These roles are installed as part of the extension distribution. CLI::Simple composes the roles associated with the selected command, rather than loading every extension role for every invocation.

This allows extensions to introduce dependencies that are needed only when their commands are executed.

role.mk

The optional role.mk file supplies GNU Make recipes.

The generated project Makefile includes extension Makefiles using:

-include $(wildcard .roles/*/role.mk)

Consequently, targets defined in an extension's role.mk become available to the project after the extension has been registered.

An extension that provides only cmb commands does not need a role.mk file.

Run:

make help

to discover documented Makefile targets, including those contributed by extensions.

Participating in Project Updates

Extensions can participate in the project update lifecycle by adding recipes to the pre-update and post-update targets.

The make update operation executes these phases in order:

An extension may augment either hook using GNU Make double-colon rules in its role.mk:

pre-update::
        @echo "Preparing extension update"

post-update::
        @echo "Updating extension files"

Double-colon rules allow multiple extensions and the project itself to contribute recipes to the same targets without replacing one another's implementations.

Extensions are responsible for managing their own project files. The presence of a role.mk file does not automatically make those files part of Bootstrapper's managed-file inventory.

An extension can use post-update to install or refresh files needed by its commands or Makefile targets.

Commands and Roles

Extension commands are registered through their role.yml manifests. When cmb recognizes a command, CLI::Simple composes the roles associated with that command using Role::Tiny.

Option specifications are registered globally across all loaded manifests. This is necessary because command-line arguments must be parsed before the selected command and its roles can be determined.

Extension authors should prefix option names with an identifier associated with their extension to avoid collisions with core options or options provided by other extensions.

For example, an extension named example might declare:

example-model=s
example-max-items=i

Option names and aliases must be unique across all registered manifests. The prefix is a convention, not a requirement imposed by CLI::Simple.

Extension Configuration

Extensions may define configuration settings in the Bootstrapper configuration file. Each extension should use a separate INI section corresponding to its option prefix.

For example:

[example]
model = standard
max-items = 25
identifier = "00123"
enabled = true

Bootstrapper combines the section name and configuration key to identify the corresponding option accessor, replacing hyphens with underscores.

The settings above map to the following accessors:

get_example_model()       set_example_model()
get_example_max_items()   set_example_max_items()
get_example_identifier()  set_example_identifier()
get_example_enabled()     set_example_enabled()

Configuration values are applied only when the corresponding setter exists and the option has not already been defined. Unrecognized settings are ignored.

Numeric values are converted to numbers, and the values true and false are converted to boolean values. Numeric-looking strings can be preserved by enclosing them in double quotes, as illustrated by the identifier setting above. The surrounding quotes are removed before the value is assigned.

The existing [cpan-maker] section is handled separately to preserve its established configuration names and behavior.

Option Precedence

Bootstrapper resolves option values according to the following precedence, from highest to lowest:

This precedence follows the CLI::Simple initialization lifecycle. Bootstrapper loads configuration settings during init(), after command-line parsing. CLI::Simple subsequently applies manifest defaults only to options that remain undefined.

Consequently, extension authors can provide sensible defaults in role.yml while allowing projects to override those defaults through Bootstrapper's configuration file.

CUSTOM BUILD TARGETS

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/bootstrap.mk
include .includes/perl.mk
include .includes/local.mk
include .includes/help.mk
include .includes/version.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:

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:

Key build variables you can override on the make command line or in config.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 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:

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 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, loads the project CI environment, resolves and installs the required build dependencies, 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. These values may also be set in builder.env.

builder.env

Before installing project build dependencies or 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 and are used when resolving the effective CI build policy.

A generated project includes:

CMB_VERSION_DRIFT=ignore
NO_ECHO=

The default builder performs a clean distribution build but does not enable syntax checking or linting as CI phase gates. Projects that want those checks may opt in through builder.env.

For example:

SYNTAX_CHECKING=on
LINT=on

When linting is enabled, provide a perltidyrc, .perltidyrc, perlcriticrc, or .perlcriticrc for each lint tool the CI build should run. The corresponding tool dependencies are installed by builder before make is invoked.

builder.env therefore provides a project-local place to define CI policy 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.

These files describe build inputs. For environment variables use builder.env; for project-specific Makefile behavior use project.mk.

perlcritic and perltidy Gates

During a CI build, the build script enables PERLTIDY and PERLCRITIC when it can find the corresponding configuration files in the project.

For reproducible CI builds, keep the project's perltidyrc and perlcriticrc aligned with the configuration used in your development environment.

Many editors and IDEs run Perltidy or Perl::Critic automatically while you work. If those tools use a personal configuration that differs from the project's checked-in configuration, code that appears clean locally may fail during build-ci.

When a project-local perltidyrc or perlcriticrc is present, build-ci uses it to enforce the project's formatting and critic policies in the clean build environment. If no project-local configuration can be discovered, the corresponding check is disabled rather than falling back to the tool's default configuration.

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

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

Why should I commit config.mk to my project?

The bootstrapper generates build-config.mk using the tools and configuration available in the current build environment. This provides a usable starting configuration without requiring the developer to specify every build setting.

However, discovered settings may differ between machines. For example, developers may have different perltidy or perlcritic configuration files, or different tools installed.

config.mk allows the project to override these discovered settings and establish a consistent build policy.

For example:

SCAN = ON
LINT = ON
SYNTAX_CHECKING = ON
PERLTIDYRC = .perltidyrc
PERLCRITICRC = .perlcriticrc

Commit config.mk and any referenced configuration files to source control so that developers and CI use the same project-defined settings.

build-config.mk, on the other hand, is generated for the current environment and should not be committed.

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.

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:

My build is failing during syntax checking with

...use_log4perl() requires Log::Log4perl...

If you are using CLI::Simple with its built-in logging support based on Log::Log4perl, you probably have something similar to this in your code:

__PACKAGE__->use_log4perl({ level => 'info' });

This requires Log::Log4perl to be installed at runtime.

CPAN::Maker::Bootstrapper scans your project to discover dependencies and installs them into an isolated build environment for syntax checking. This prevents modules installed in the developer's local environment from masking undeclared dependencies.

Because your code does not directly load Log::Log4perl, the dependency scanner may not discover it. Furthermore, CLI::Simple declares Log::Log4perl as an optional dependency, so it is not automatically installed.

You can explicitly declare the dependency in either of two ways:

The + prefix instructs CPAN::Maker::Bootstrapper to retain this manually declared dependency when rescanning your project.

Note: The same approach applies to other required modules that the dependency scanner cannot discover. See "Dependencies Management".

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::Bootstrapper::ConfigReader - the config reader bundled with this distribution, available for use in your own tools.

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.6.1

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.