A per-project Perl environment manager built on top of
perlbrew and cpanm: pin a Perl version, declare your
prereqs, and ship a single self-contained installer that boots a complete
./perl5/ from scratch on any target host.
Think of it as Carton with two extra things baked in:
- Perl version pinning via perlbrew. Your
mistfiledeclaresperl '5.20.3'; the installer drives perlbrew to make sure that exact Perl is available (installing it if needed and permitted) and re-execs itself under it before touching any modules. mist never builds Perl on its own — it just orchestrates perlbrew. See Perl versions and perlbrew below. - A fatpacked, self-contained installer.
mist compileproducesmpan-install— a single Perl script that embedscpanm, the prereq list, and (optionally) a bundled CPAN mirror in./mpan-dist/. The target host needs only a system Perl (plus a C toolchain) to bootstrap; no network access to CPAN is required when the bundle is used as a mirror.
The combination is what makes mist useful where Carton alone falls short: reproducible builds on hosts that may not match your dev box's Perl, and deployments that survive CPAN outages or removed module versions.
There are exactly two commands you usually run:
mist compile— readsmistfile+cpanfile, regenerates./mpan-install. Run this whenever you edit the mistfile or change prereqs../mpan-install— the generated installer. Run this on any host (dev or prod) to populate./perl5/with the right Perl and all dependencies. Re-run it aftermist compileto pick up changes.
Then, to run code under the project's Perl:
./mist-run <cmd…>— a thin wrapper that activates./perl5/and execs the given command. Use it forprove,perl, scripts, one-offs:./mist-run -- perl -Ilib -MMy::Module -e '...'.
./mist-run mist compile looks reasonable. It is not.
mist-run exports the project's pinned-Perl PERL5LIB / local::lib env and
then execs its argv. The system mist binary has a shebang pointing at
whatever Perl is current on the box (often 5.38+), so it ends up trying to
load XS modules built for the project's 5.20. First failure is usually
Sub::Identify.so: undefined symbol: PL_sv_no, followed by a cascade of
B::Hooks::EndOfScope explosions.
Rule: invoke mist directly (it manages the project's Perl; it doesn't
run under it). Reserve mist-run for tools that should run under the
project's Perl.
A mistfile is Perl code, not data. It's eval'd in a sandbox that exposes
seven directives — these are the complete set, from
lib/Mist/Environment.pm:8:
Pin the Perl version. The installer will refuse to proceed unless perlbrew has that version available (it will tell you how to install it).
Install this module before the cpanfile prereqs, in the order listed.
Use for modules that must be present before normal dependency resolution
(toolchain-ish things: ExtUtils::MakeMaker, Module::Build, certain
List::Util versions). The optional second argument is a version spec
('1.23', '>= 1.23', etc.); if omitted, mist will pick up any version
requirement from cpanfile.
Install this module with --notest. Use sparingly — the usual reason is a
module whose test suite is broken on your target Perl or in CI environments
without a display/network/etc.
A block of Perl that runs at install time, before any modules are
installed. Use it to fail fast on missing system dependencies instead of
letting cpanm chew through CPAN for an hour and then bomb on a missing
mysql_config.
The block runs in an environment where three helpers are pre-imported (no
use needed):
check_bin("name")— fromDevel::CheckBin. Truthy if the binary is on$PATH. Use for required external programs (mysql_config,pg_config,pdftotext,qpdf, …).assert_lib(lib => 'name', header => '...')— fromDevel::CheckLib. Dies on failure. Wrap ineval { } ; die "$@\n…hint…\n" if $@;if you want to add a helpful "On Debian, installfoo-dev" message.check_c99()— fromDevel::CheckCompiler. Truthy if a C99 compiler is available. (There's noassert_compile; chain it withor dieyourself.)
Probe::Perl is also imported into the sandbox if you need to query the
running Perl's Config without useing anything.
Idiomatic example (adapted from examples/mistfile.with-assertions):
assert {
check_c99() or die "No C compiler found";
eval { assert_lib( lib => 'z', header => '' ) };
die "$@\nOn Debian, try installing zlib1g-dev\n" if $@;
assert_lib( lib => [qw/ ssl xml2 expat Imlib2 /] );
check_bin( "mysql_config" ) or die
"mysql_config not found — install default-libmysqlclient-dev\n";
};Pull in another mist-managed project's mistfile as a sub-distribution. Inside
the block, the same DSL applies (perl, prepend, notest, assert, plus
dist_path). This is how shared dependency sets between sibling repos work:
project A's mistfile merges project B's, picking up B's prereqs and
asserts.
Marker convention: when a merge block is managed by external tooling (e.g. generated/refreshed by a script), wrap it in marker comments so humans and tools know not to hand-edit:
### <<<[Other::Dist] - keep this line intact
merge 'Other::Dist' => sub {
perl '5.20.3';
prepend 'Another::Module';
};
### [Other::Dist]>>> - keep this line intactAnything between the <<<[Name] and [Name]>>> markers is a managed block.
Only valid inside a merge block. Tells mist where to find the merged
project's source on disk. Resolution order (see
App::Mist::Context::get_merge_path_for):
dist_pathrelative to project root, if that exists and is readable;dist_pathrelative to$HOME, same check;- Fallback: sibling-directory convention — CWD +
../+ lowercased dist name (Other::Dist→../other-dist).
Register a script to run before (prepare) or after (finalize) module
installation. Phases other than prepare / finalize raise an error at
parse time.
The code is split along the build-master / host divide:
lib/App/Mist/— themistCLI itself (App::Cmd-based). Each subcommand is a file underlib/App/Mist/Command/. Start here for anything you'd invoke asmist <subcommand>.lib/App/Mist/Context.pmis the shared object that holds project state (project_root,cpanfile, parsedmistfile, perlbrew root, …).
lib/Mist/— the model and the host-side runtime.lib/Mist/Environment.pmandlib/Mist/Distribution.pmare the mistfile parser and parse result.lib/Mist/Script/{perlbrew,install,usage}.pmare the modules that get fatpacked intompan-installand run on the target host. They never run as part of themistCLI.
script/mist— the CLI entry point (a four-line shim intoApp::Mist->run).mpan-install— the generated installer artifact. Do not hand-edit. Regenerate withmist compile.mpan-dist/— the bundled CPAN-shaped mirror of pinned tarballs.mist inject,mist upgrade, andmist indexcurate it.examples/,t/share/— sample mistfiles worth reading for the shape of real-world usage.
docs/workflow.txt— the original build-master vs. host mental model.docs/bootstrap.shandrebuild-5.20.3.sh— how to rebuild mist's own bundledmpan-dist/when the chicken-and-egg installer itself is broken.
Sebastian Willert <s.willert@wecare.de>