CI

NAME

App::Ariza::CI - the GitHub Actions workflows that build, smoke and publish an app's bundles

SYNOPSIS


use App::Ariza::CI;
use App::Ariza::Config;

my $cfg = App::Ariza::Config.load('/path/to/App-Moneymoor'.IO);

my @done = App::Ariza::CI.write(
    :out-dir('/path/to/App-Moneymoor/.github/workflows'.IO),
    :config($cfg),
);
say @done.map({ "{.<action>} {.<output>}" });
# (skipped test.yml wrote release.yml)

# Just the text, for a diff or a test:
say App::Ariza::CI.render(
    :template<release.yml.j2>,
    |App::Ariza::CI.context(:config($cfg)),
);

# What this app's release workflow will contain:
say App::Ariza::CI.lanes-for($cfg).map(*.<job>);
# (bundle-macos-arm64 bundle-linux-x86_64-glibc bundle-windows-x86_64)

DESCRIPTION

App::Ariza::Bundle builds one bundle on one machine. A release needs one per platform, each built on that platform, each proved to run, and all of them published together — which is a CI problem, and this is the CI.

ariza scaffold-ci writes two files into an app's .github/workflows/:


test.yml      the ordinary Raku test workflow, written once
release.yml   build every declared platform, smoke each, publish on a tag

Neither is ariza's to run. They are committed to the app's repository and run by GitHub, exactly as install.sh is committed and run by a user — ariza's job is to make sure the file says the right thing about this app.

What release.yml does

One build job per slug in the app's bundle.platforms, in parallel:

  • bundle-macos-arm64 — macos-latest, Rakudo from Raku/setup-raku, SQLCipher from brew install sqlcipher.

  • bundle-linux-x86_64-glibc — ubuntu-latest inside the quay.io/pypa/manylinux_2_28_x86_64 container, so the archive's glibc floor is 2.28 rather than whatever the runner image ships this month. Rakudo comes from the rakudo.org release index by hand (setup-raku installs into the runner's tool cache, which is not in the container's filesystem) and SQLCipher is built from source at the pinned version.

  • bundle-windows-x86_64 — windows-latest, setup-raku, and SQLCipher from MSYS2's mingw-w64-ucrt-x86_64-sqlcipher, installed with the pacman every windows runner already has, with SQLCIPHER_LIB_DIR pointed at the result — which is the sourcing contract App::Ariza::Native documents for Windows. The UCRT package rather than vcpkg's port, because an MSVC-built SQLCipher imports vcruntime140.dll, which is not part of Windows and which the PE audit therefore refuses to ship.

Each of them then runs the same three steps — ariza bundle, ariza smoke, upload the archive and its .sha256 — because the difference between platforms is entirely in what has to be installed before ariza can start.

Then, on a tag only:

  • publish — collects every lane's artefact, flattens them, recomputes a combined checksums.txt, checks that against the sidecars ariza wrote, and creates the GitHub release with a body that says what a bundle is, which machines each archive runs on, and how to verify a download.

  • smoke-installer-macos-arm64, smoke-installer-linux-x86_64-glibc, smoke-installer-windows-x86_64 — one per declared platform with a clean-machine smoke recipe (%LANES's smoke key), each on a plain runner with nothing installed on it: it downloads the archive that was just published, installs it with the repository's own committed install.sh or install.ps1, and runs the installed launcher under a stripped environment (env -i on POSIX; a from-scratch System.Diagnostics.Process on Windows, which has no env -i). These are the only jobs that test the artefact a user will actually receive, through the path they will actually take — macOS's incidentally being the only place install.sh's BSD branches (shasum -a 256, bsdtar) ever run in CI. Scaffolded only for an app with an installer.repo to publish to; a declared platform with a build lane but no smoke recipe yet renders no job for it, and a comment in the workflow says so instead of leaving the gap silent.

Dispatch before you tag

The workflow triggers on workflow_dispatch as well as on push: tags: ['v*'], and the dispatch run stops after the build lanes: publish and every smoke-installer-* job are gated on startsWith(github.ref, 'refs/tags/').

That is the iteration loop, and it is the reason the dispatch trigger exists. A recipe that is wrong — a package that has been renamed, a runner image that has moved on — costs a run and a push to a branch, rather than a burnt tag and a deleted release. The dispatch input ref takes a branch, so the lane being fixed does not have to be on the default branch to be tried.

Which Raku is which

Every lane installs a Raku to run ariza with. It is not the runtime that ends up in the bundle: ariza downloads the pinned Rakudo ([rakudo] in versions.toml) for itself, verifies it, and unpacks that into the archive. The lane's own Rakudo can be any version at all, which is why setup-raku is pinned to latest rather than to anything.

The manylinux lane is the exception in mechanism rather than in principle: setup-raku cannot install into a container, so the lane resolves the pin against the same rakudo.org JSON index App::Ariza::Rakudo reads and unpacks the archive itself. There is no URL to construct — upstream filenames carry a toolchain suffix — so the index is the only honest route.

What is regenerated, and what is yours

release.yml is derived from bundle.platforms: add a platform to ariza.toml, re-run ariza scaffold-ci, and the file gains a lane and a needs: entry. It is rewritten in place every time, so hand edits to it are edits you will make twice.

test.yml is written only when it is absent. A test workflow grows system dependencies, extra jobs and skip conditions that no generator can infer from a manifest — this one is a starting point in the house shape, and the moment it is committed it belongs to the repository. :force overwrites it anyway, for when the house shape has moved on and the local edits are known to be nothing.

The generated header of each file says which of the two it is, so nobody has to remember.

Where ariza comes from

By default the lanes run zef install --/test 'App::Ariza:ver<0.2.2+>:auth<zef:apogee>', with the repository URL beside it as a comment. ci.ariza-source in the app's ariza.toml swaps them:


[ci]
ariza-source = "https://github.com/m-doughty/App-Ariza.git"

That is the bootstrap case — an app whose release workflow has to exist before ariza is published — and the case where a change to ariza is being tested against a real release before it is cut. Whichever is not in use is rendered as a comment on the next line, along with the version of ariza that scaffolded the file, so a reader can tell what produced it and swap without looking anything up.

METHODS

write(:$out-dir!, :$config!, :$versions, :$ariza-version, :$force --> List)

Render the workflows and return one { path, output, action } per file, action being 'wrote' or 'skipped'.

The output directory is created if it does not exist — unlike App::Ariza::Installer's, which refuses. .github/workflows has one spelling and, in a repository with no CI, is by definition not there yet, so a missing one is the expected case rather than a typo.

render(:$template!, *%ctx --> Str)

One template, as text, always with LF line endings. Exposed separately because that is what a golden-file test compares.

context(:$config!, :$versions, :$ariza-version --> Hash)

The render context: the app's names, the pins the workflows quote, the declared lanes and their job ids, the platform floors for the release body, and lane_jobs — release.yml's whole jobs: section, rendered from one template per lane and pasted in. A macOS lane and a manylinux one share their last three steps and nothing else, so they are separate files rather than a template full of conditionals.

Dies when the app declares no platforms, when versions.toml has no [rakudo] pin (or a revision that is not a number, which the manylinux lane compares numerically against the release index), or when a linux-x86_64-glibc lane is wanted and there is no sqlcipher pin for it to build.

lanes-for(App::Ariza::Config $config --> List) / lane-for(Str $slug --> Hash) / lane-slugs(--> List)

The lanes an app gets in manifest order, one slug's recipe, and every slug ariza can scaffold a lane for.

An undeclarable slug is a die rather than a skip, for the same reason an unknown platform in bundle.platforms is: silently dropping a platform the author asked for produces a release quietly missing it. The set is deliberately the same shape as App::Ariza::Rakudo's — a platform with no official Rakudo build has no bundle to publish, so a lane for it would be a guess.

workflows(--> List)

The { template, output, overwrite } entries, in write order.

install-lines(App::Ariza::Config $config, :$ariza-version! --> List)

(command, @note-lines): the zef install a lane runs, and the comment rendered beside it — which names the alternative source and the ariza version that scaffolded the file, wrapped, because a workflow is read as text.

ariza-version(--> Str)

ariza's own version for the provenance line, or 'dev' from a checkout.

SEE ALSO

App::Ariza::Bundle and App::Ariza::Smoke, which are what every build lane runs; App::Ariza::Installer, whose install.sh the installer smoke job drives; App::Ariza::Native, whose Windows sourcing contract the pacman step satisfies; App::Ariza::Rakudo, whose release-index lookup the manylinux lane repeats in shell.

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.

App::Ariza v0.2.4

bundler and distribution tool for Raku terminal apps

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

Config::TOML:ver<0.1.3+>:auth<zef:raku-community-modules>JSON::Fast:ver<0.19+>:auth<cpan:TIMOTIMO>Template::Jinja2:ver<0.3.0+>:auth<zef:apogee>

Test Dependencies

Provides

  • App::Ariza
  • App::Ariza::Bundle
  • App::Ariza::CI
  • App::Ariza::Config
  • App::Ariza::Installer
  • App::Ariza::Launcher
  • App::Ariza::Licensing
  • App::Ariza::Native
  • App::Ariza::Platform
  • App::Ariza::Rakudo
  • App::Ariza::Resources
  • App::Ariza::Runner
  • App::Ariza::Site
  • App::Ariza::Smoke
  • App::Ariza::Tools
  • App::Ariza::Update
  • App::Ariza::Versions

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite — the markup and publishing tools behind this site.