Rakudo
NAME
App::Ariza::Rakudo - fetch, cache and unpack the Rakudo runtime a bundle embeds
SYNOPSIS
use App::Ariza::Rakudo;
use App::Ariza::Versions;
my %rt = App::Ariza::Rakudo.provision(
:bundle-dir($work),
:slug<macos-arm64>,
:versions(App::Ariza::Versions.load),
);
say %rt<root>; # ā¦/moneymoor-0.2.0-macos-arm64/rakudo
say %rt<raku>; # ā¦/rakudo/bin/raku
say %rt<zef>; # ā¦/rakudo/share/perl6/site/bin/zef
say %rt<tag>; # 2026.07-01
say %rt<sha256>; # 9f3cā¦
say %rt<cached>; # True on the second build
# The pieces, separately:
my @entries = App::Ariza::Rakudo.fetch-index;
my %entry = App::Ariza::Rakudo.select-entry(@entries,
:slug<linux-x86_64-glibc>, :version<2026.07>, :revision<01>);
say %entry<url>;
DESCRIPTION
A bundle carries its own interpreter, so nothing about the user's machine ā whether Raku is installed, which version, which module repositories are in the chain ā can affect it. This module puts that interpreter in the bundle.
The runtime is not built: it is the official archive from
rakudo.org, pinned in App::Ariza::Versions by
[rakudo] version and revision, downloaded once and cached for
every subsequent build.
The release index
https://rakudo.org/dl/rakudo serves a JSON array, one object per
downloadable file, covering every release since 2009. The fields that
matter are platform (macos, linux, win, src), arch,
ver, build_rev, type (only archive is a runtime; the rest
are signatures and checksum files) and url.
Matching is exact on all five. A pin of 2026.07 / 01 selects
build_rev 1 and nothing else ā never "the newest 2026.07", never
"whatever is latest" ā because the whole point of the pin is that two
builds a month apart embed the same bytes.
Which platforms can be bundled
rakudo.org publishes binaries for four of the eight slugs App::Ariza::Platform knows:
macos-arm64,macos-x86_64linux-x86_64-glibcwindows-x86_64
The others ā musl, Linux aarch64, Windows on ARM ā are real platforms with no upstream archive to embed. Asking for one dies naming the four that work, rather than assembling a URL that will 404. Making one of them bundleable means building a runtime and publishing it somewhere this map can point at; it is not a code change here.
Caching: record, then verify
Archives land in $XDG_CACHE_HOME/ariza/rakudo (~/.cache/⦠when
that is unset), each beside a .sha256 sidecar written at download
time.
There is no upstream checksum to compare against ā rakudo.org publishes
one, but fetching it only moves the trust boundary ā so the digest is
recorded on first download and verified on every reuse. That
catches exactly the failure this cache can actually suffer: a file that
changed after it was written, because a process was killed mid-transfer
or a disk lied. A mismatch is not fatal; the cached copy is discarded,
noted on STDERR, and re-downloaded.
Downloads themselves land in a .part file and are renamed on success
(see App::Ariza::Tools), so an interrupted transfer is never mistaken
for a cache hit. An archive that is present but has no sidecar ā dropped
in by hand, or left by a much older run ā is adopted: its digest is
recorded now and verified from then on.
METHODS
provision(:$bundle-dir!, :$slug!, :$versions!, :$cache-dir, :$url, :&fetch, :&download --> Hash)
The whole job: resolve the pin against the index, fetch or reuse the
archive, unpack it to < <bundle-dir>/rakudo > (stripping the wrapper
directory), and verify that the two entry points later stages need
actually exist.
Returns root, raku, zef, archive, sha256, cached,
url, version, revision and tag.
fetch-index(:$url, :&fetch --> List)
The index, parsed. An unreachable host dies saying so explicitly ā "a bundle needs network access the first time it is built" ā rather than as a JSON parse error on an empty body.
select-entry(@entries, :$slug!, :$version!, :$revision! --> Hash)
The one matching archive entry. No match dies listing the most recent versions the index does have for that platform, so a stale pin is obvious. Two matches also dies: upstream publishing two archives for one platform, version and revision (a second toolchain, say) is a choice that belongs in the pin file, not in a tiebreak here.
fetch-archive(%entry, :$cache-dir, :&download --> Hash)
Download or reuse. Returns archive, sha256 and cached.
unpack(IO() $archive, IO() $bundle-dir --> IO::Path)
Extract into < <bundle-dir>/rakudo >, stripping the single top-level
directory the archive wraps everything in. Any existing rakudo/ is
replaced, so a rebuild into the same workdir is clean.
raku-bin(IO() $root, :$slug --> IO::Path) / zef-bin(IO() $root, :$slug --> IO::Path)
The interpreter and the zef entry point inside an unpacked runtime
(raku.exe on Windows).
On POSIX, zef-bin is a shell wrapper, not a Raku script ā it finds
its sibling raku relocatably and execs it. Run it directly. Passing it
to bin/raku as a script is a syntax error, because it is sh.
The official Windows zip ships no zef.bat, so on Windows zef-bin
returns zef.raku instead ā the .raku stub every zef install ships
(sub MAIN(*@, *%) { CompUnit::RepositoryRegistry.run-script("zef") }),
byte-identical across platforms. It has to be run under the bundled
raku, not exec'd; App::Ariza::Site is where that invocation is
built. The zip also ships a compiled zef.exe, but a compiled wrapper's
raku-discovery behaviour inside a relocated bundle is unverified, while
the run-script stub depends on nothing but the raku it is handed.
cache-dir(--> IO::Path) / fetchable-slugs(--> List) / index-platform(Str --> Hash)
The cache root, the bundleable slugs, and a slug's index coordinates.
SEE ALSO
App::Ariza::Versions for the pin, App::Ariza::Site for what is installed into the runtime once it is unpacked.
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.