Day 24 – Packaging and unpackaging real good

After all Rakuing along all Christmas, Santa realizes it’s a pretty good idea to keep things packed and ready to ship whenever it’s needed. So it looks at containers. Not the containers that might or might not actually be doing all the grunt work for bringing gifts to all good boys and girls in the world, but containers that are used to pack Raku and ship it or use it for testing. Something you need to do sooner or later, and need to do real fast.

The base container

The base container needs to be clean, and tiny, and contain only what’s strictly necessary to build your application on. So it needs a bunch of binaries and that’s that. No ancillary utilities, nothing like that. Enter jjmerelo/raku, a very bare bones container, that takes 15 MBytes and contains only the Rakudo compiler, and everything it needs to work. It’s also available from GHCR, if that’s more to your taste.

You only need that to run your Raku programs. For instance, just print all environment variables that are available inside the container:

time podman run --rm -it ghcr.io/jj/raku:latest -e 'say %*ENV'

Which takes around 6 seconds in my machine, most of it devoted to downloading the container. Not a bad deal, really, all things considered.

The thing it, it comes in two flavors. Alternative is called jj/raku-gha, for obvious reasons: It’s the one that will actually work in side GitHub actions, which is where many of you will eventually use it. The difference? Well, a tiny difference, but one that took some time to discover: its default user, called raku, uses 1001 as UID, instead of the default 1000.

Right, I could have directly used 1001 as the single UID for all of them, but then I might have to do some more changes for GitHub Actions, so why bother?

Essentially, the user that runs GitHub actions uses that UID. We want our package user to be in harmony with the GHA user. We achieve harmony with that.

But we want a tiny bit more.

We will probably need zef to install new modules. And while we’re at it, we might also need to use a REPL in an easier way. Enter alpine-raku, once again in two flavors: regular and gha. Same difference as above: different UID for the user.

Also, this is the same jjmerelo/alpine-raku container I have been maintaining for some time. Its plumbing is now completely different, but its functionality is quite the same. Only it’s slimmer, so faster to download. Again

time podman run --rm -it ghcr.io/jj/raku-zef:latest -e 'say %*ENV'

Will take a bit north of 7 seconds, with exactly the same result. But we will see an interesting bit in that result:

{ENV => /home/raku/.profile, HOME => /home/raku, HOSTNAME => 2b6b1ac50f73, PATH => /home/raku/.raku/bin:/home/raku/.raku/share/perl6/site/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, PKGS => git, PKGS_TMP => make gcc linux-headers musl-dev, RAKULIB => inst#/home/raku/.raku, TERM => xterm, container => podman}

And that’s the RAKULIB bit. What I’m saying it is that, no matter what the environment says, we’re gonna have an installation of Raku in that precise directory. Which is the home directory, and it should work, right? Only it does not, because GitHub Actions change arbitrarily the HOME variable, which is where Raku picks it from.

This was again something that required a bit of work and understanding where Rakudo picks its configuration. If we run

raku -e 'dd $*REPO.repo-chain'

We will obtain something like this:

(CompUnit::Repository::Installation.new(prefix => "/home/jmerelo/.raku"),CompUnit::Repository::Installation.new(prefix => "/home/jmerelo/.rakubrew/versions/moar-2021.10/install/share/perl6/site"), CompUnit::Repository::Installation.new(prefix => "/home/jmerelo/.rakubrew/versions/moar-2021.10/install/share/perl6/vendor"), CompUnit::Repository::Installation.new(prefix => "/home/jmerelo/.rakubrew/versions/moar-2021.10/install/share/perl6/core"), CompUnit::Repository::AbsolutePath.new(next-repo => CompUnit::Repository::NQP.new(next-repo => CompUnit::Repository::Perl5.new(next-repo => CompUnit::Repository))), CompUnit::Repository::NQP.new(next-repo => CompUnit::Repository::Perl5.new(next-repo => CompUnit::Repository)), CompUnit::Repository::Perl5.new(next-repo => CompUnit::Repository))

We’re talking about the repository chain, where Raku (through Rakudo) keeps the information or where to find the, effectively, CompUnit repositories or the libraries, precompiled (those are the CompUnit::Repository::Installation) or not (CompUnit::Repository::AbsolutePath). But let’s look at the first one, which is where it will start looking. It’s effectively our home directory, or more precisely, a subdirectory where things are installed in the normal course of things. Where does Rakudo picks that from? Let’s change the HOME environment variable and we’ll see, or rather not, because depending on the installation, it will simply hang. With the RAKULIB defined as above, however, say $*REPO.repo-chain will print

(inst#/home/raku/.raku inst#/tmp/.raku inst#/usr/share/perl6/site inst#/usr/share/perl6/vendor inst#/usr/share/perl6/core ap# nqp# perl5#)

Our CompUnit::Repository::Installation become here inst#/home/raku/.raku, but, what’s more important, the HOME environment variable gets tacked a .raku in the end and an inst# in front, implying that’s the place where Rakudo expects to find it.

This brings us back again to GitHub actions, which change that variable for no good reason, leaving our Rakudo installation effectively unusable. But no fear, a simple environment variable baked in the alpine-raku container (and its GHCR variants) will keep the actual Rakudo installation in check for GitHub actions to come.

Now we’re all set

And we can write our own GitHub actions using this image. Directly run all our stuff inside a container that has Raku. For instance, this way:

name: "Test in a Raku container"
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
permissions:
packages: read
container:
image: ghcr.io/jj/raku-zef-gha
steps:
– name: Checkout
uses: actions/checkout@v2
– name: Install modules
run: zef install .
– name: Test
run: zef –debug test .
view raw raku-test.yaml hosted with ❤ by GitHub
GHA used in Pod::Load

This is decevingly simply, doing exactly what you would do in your console. Install, and then test, right? That’s it. Underneath, however the fact that the container is using the right UID and Raku knows where to find its own installation despite all the moving and shaking that’s going on is what makes it run.

You can even do a bit more. Use Raku as a shell for running anything. Add this step:

  - name: Use Raku to run
    shell: raku {0}
    run: say $*REPO.repo-chain

And with the shell magic, it will actually run that directly on the Raku interpreter. You can do anything else you want: install stuff, run Cro if you want. All within the GitHub action! For instance, do you want to chart how many files were changed in the latest commits using Raku? Here you go:

– name: Install Text::Chart
run: zef install Text::Chart
– name: Chart files changed latest commits
shell: raku {0}
run: |
use Text::Chart;
my @changed-files = qx<git log –oneline –shortstat -$COMMITS>
.lines.grep( /file/ )
.map( * ~~ /$<files>=(\d+) \s+ file/ )
.map: +*<files>;
say vertical(
:max( @changed-files[0..*-2].max),
@changed-files[0..*-2]
);
This can be added to the action above

Couple of easy steps: install whatever you need, and then use Text::Chart to chart those files. This needs a bit of explaining, or maybe directly checking the source to have the complete picture: it’s using an environment variable called COMMITS, which is one more than the commits we want to chart, has been used to check out all those commits, and then, of course, we need to pop the last one since it’s a squashed commit that contains all older changes in the repo, uglifying our chart (which we don’t want). Essentially, however, is a pipe that takes the text content of the log that includes the number of file changed, extracts that number via a regex match, and feeds it into the vertical function to create the text chart. Which will show something like this (click on the > sign to show the chart):

Files changed in the last 10 commits in Pod::Load

With thousands of files at your disposal, the sky’s the limit. Do you want to install fez and upload automatically when tagging? Why not? Just do it. Upload your secret, and Bob’s your uncle. Do you want to do some complicated analytics on the source using Raku or generate thumbnails? Go ahead!

Happy packaging!

After this, Santa was incredibly happy, since all his Raku stuff was properly checked, and even containerized if needed! So he sit down to enjoy his corncob pipe, which Meta-Santa brought for him last Christmas.

And, with that, Merry Christmas to all and everyone of you!

Published by jjmerelo

Servidor de ustedes

6 thoughts on “Day 24 – Packaging and unpackaging real good

  1. Great article – I got 11sec to start on macOS / vftools / ubuntu / docker … then sadly <<The requested image’s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8)>> … any plans for linux/arm64?

    Liked by 1 person

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.