Bundle
NAME
App::Ariza::Bundle - build one self-contained archive of an application
SYNOPSIS
use App::Ariza::Bundle;
my %b = App::Ariza::Bundle.build(
:app-dir('/Users/me/code/App-Moneymoor'),
:out-dir('/tmp/dist'),
);
say %b<name>; # moneymoor-0.2.0-macos-arm64
say %b<archive>; # /tmp/dist/moneymoor-0.2.0-macos-arm64.tar.gz
say %b<sha256>;
say %b<compressed>; # 76_226_140
say %b<uncompressed>; # 214_913_002
# Cross-target: a Linux bundle needs a Linux SQLCipher, which this
# machine's package manager cannot supply.
App::Ariza::Bundle.build(
:app-dir($app), :platform<linux-x86_64-glibc>,
:sqlcipher-archive('/tmp/libsqlcipher-linux-x86_64.tar.gz'));
DESCRIPTION
The orchestrator. Everything a bundle needs is done by another module in this distribution; this one decides the order, names the artefact, and writes the three files that describe what was made.
The order, and why it is that order
App::Ariza::Rakudo unpacks the pinned runtime into
< <work>/rakudo >. It goes first because everything after it runs under that runtime.App::Ariza::Site installs the app and its closure into the runtime's own
vendorrepository ā< <work>/rakudo/share/perl6/vendor >, which is the only kind of place a warm precompilation store survives being moved to another machine ā using the runtime's ownzef, stages notcurses into< <work>/native >as a side effect, and warms that store.App::Ariza::Native stages SQLCipher, then audits every native binary in the bundle. The audit runs after both staging steps because it is a statement about the finished bundle, not about any one component.
App::Ariza::Launcher writes
< <work>/bin/<exec> >and stages the compiled Windows runner, last, because it needs the target App::Ariza::Site discovered and the library path App::Ariza::Native chose.App::Ariza::Licensing then reads the finished bundle ā every native pack's licensing kit, every installed distribution's metadata ā and writes
THIRD-PARTY.mdandLICENSES/. It runs before the manifest because the manifest records its summary, and a summary written after the fact is one that can disagree with the document beside it.
Then VERSION and ariza-manifest.json are written and the whole
directory is tarred.
The artefact
<out-dir>/moneymoor-0.2.0-macos-arm64/ # the bundle, left in place
<out-dir>/moneymoor-0.2.0-macos-arm64.tar.gz # the thing you publish
<out-dir>/moneymoor-0.2.0-macos-arm64.tar.gz.sha256
The archive holds exactly one top-level directory, named after the
bundle, so unpacking it anywhere is predictable and never scatters files
into the current directory. The checksum file uses the
< <hex> <file> > shape shasum -c and sha256sum -c read.
The unpacked workdir is deliberately not deleted: it is what
ariza smoke can be pointed at without a round-trip through tar, and
what you look inside when something is wrong.
What is inside
bin/moneymoor the launcher, and the only thing a user runs
rakudo/ the interpreter (plus SQLCipher, on macOS)
share/perl6/vendor/ every Raku module, with warm bytecode
native/ notcurses and friends
VERSION one screen: app version and component pins
ariza-manifest.json the machine-readable version of the same
THIRD-PARTY.md every component, its licence and where that
fact came from
LICENSES/ the text of every licence the above cites
VERSION and the manifest
VERSION is for a human in a bug report: the app, the platform, and
one line per pinned component. ariza-manifest.json is the same facts
plus the ones only a machine cares about ā every source URL, every
SHA-256, every Raku distribution installed with its version and author,
and the smoke commands, so ariza smoke can check an archive it knows
nothing else about.
THIRD-PARTY.md and LICENSES/
A bundle redistributes other people's software, so it says so ā in one document listing every component with its version, its licence, its copyright and where that fact came from, and one directory holding the text of every licence it cites.
None of it is written down in ariza. App::Ariza::Licensing reads a
native pack's own licensing kit, ariza's maintained data file for the
vendored runtime and MoarVM's vendored C libraries, the license field
of every distribution installed into the bundle, and the app's
ariza.toml. A component it cannot attribute is a visible row and a
warning rather than a silence, and licensing.strict in the app's
config turns that into a failed build.
Declared platforms are enforced
Building a slug the app does not list in bundle.platforms is an
error. An app that lists its platforms has made a statement about which
ones it is tested on, and producing an artefact named
<app>-<ver>-<slug> for one it never claimed
is a promise ariza has no business making on its behalf. An app with no
bundle.platforms at all imposes no such constraint.
METHODS
build(:$app-dir!, :$platform, :$out-dir, :$sqlcipher-archive, :$verbose --> Hash)
The whole build. :$platform defaults to this machine's slug;
:$out-dir to the current directory; :$sqlcipher-archive stages a
local SQLCipher archive instead of taking the machine's own copy, which
is what a cross-build or an air-gapped build needs.
Returns name, dir, archive, checksum, sha256,
compressed, uncompressed, manifest, launchers, audit,
licensing, platform and version.
bundle-name(:$exec!, :$version!, :$platform! --> Str)
< <exec>-<version>-<platform> > ā the workdir name and the archive
stem.
version-file(%manifest --> Str)
The text of VERSION, rendered from a manifest. Public because it is
the one artefact whose exact wording a human reads in a bug report, and
because rendering it is worth testing without building 165MB first.
runner-component(%runner, IO::Path $work --> Hash)
The manifest's components.runner entry: the published artefact that
was staged, the release it came from, its URL and the digest it was
verified against. The runner is downloaded like the runtime archive is,
so it is recorded like the runtime archive is ā without this it would be
the one binary in a bundle a reader could not trace back to something
published, which for the file a Windows user actually runs is the worst
place to have a gap.
notcurses-component(%site --> Hash)
The manifest's Notcurses tag and exact staged lib/ directory from
App::Ariza::Site. Windows launchers and smoke consume the same path;
reconstructing it independently would let metadata and the live loader
contract drift apart.
sqlcipher-component(%sqlcipher --> Hash)
The manifest's components.sqlcipher entry, built from what
App::Ariza::Native staged: the version staged (or 'unknown'),
the pinned one beside it, the bundle-relative path, the digest, and
the provenance sentence. Public because "does the manifest tell the
truth about what was bundled" is worth a test that does not build 165MB
first.
app-version(IO() $app-dir --> Str)
The version from the checkout's META6.json. Read from source rather
than from the installed distribution because the workdir has to be named
before anything is installed.
SEE ALSO
App::Ariza::Smoke, which takes the archive this produces and proves it runs somewhere else.
AUTHOR
Matt Doughty
COPYRIGHT AND LICENSE
Copyright 2026 Matt Doughty
This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.