Code And Cocktails

Two Things I Learned About GitHub Actions

| Comments

Background

TLDR: I have GitHub Action Workflows that run Common Lisp code; the setup of the Workflow containers was duplicated and the duplication was going to be a problem. Learned: 1) the lines of the jobs.steps.run will be placed in a file; jobs.steps.shell is a command that will take that file as an argument ({0} can be used as a placeholder for that file) 2) actions are not hard to make, and docker actions have some particular ways of working that can be surprising.

In my work as the Common Lisp Exercism Track maintainer I try to do as much as I can in Common Lisp just to use it more and expand my knowledge of it. This post is not about Common Lisp however.

The track has two GitHub Action Workflows which need to run Common Lisp code for its CI (one to run all the tests and the other to do some checking of the validity of the track’s configuration). Originally the workflows used a standard ubuntu-latest container and then installed SBCL and QuickLisp (both downloading the QuickLisp script and running it), setting those both up to make sure they could find the code, then loading the system (via QuickLisp to download any dependencies it may have) then finally calling the ASDF:TEST-SYSTEM function on the system ("TEST-EXERCISES" for example). (See the original test-exercises workflow code for an example.)

If it were only a single workflow this would not be too bad. But there were two workflows and I might want to make more. And there are other repositories for the Common Lisp track which I also maintain which I did not yet have CI set up and knew I’d need to do this same sort of thing. So this needed to be cleaned up.

First thing I learned

I knew that the clfoundation/sbcl docker image had SBCL installed and could easily be set up for QuickLisp so I wanted to use that somehow as the base of my workflows.

You can do this per workflow job by setting the container.image setting. This was a good step, my workflow no longer needed to download and install SBCL or download the QuickLisp script.

So now my job.steps.run could easily access SBCL and the QuickLisp script to set things up and run my code. But then I learned something to make the running of my code better.

steps can have a shell setting which is the name of a command which is given a file. The file contains all the lines of the run in the same step. Better yet shell can be of the form cmd arg1 arg2... {0} other-args...where {0} will be replaced with that filename. So that means I could run lisp code as my run:

      - name: Run Tests
        run: |
          (ql:quickload "test-exercises")
          (asdf:test-system "test-exercises")
        shell: sbcl --noinform --non-interactive --load {0}

(See the workflow using an published image as base for this example.)

The second thing I learned

NOTE: This learning is mostly a trial-and-error learning what didn’t work.

While the above was definitely good there was still duplicated setup and commands. What I wanted was my own action for performing an operation on a system.

I created an action in the same repository which could then be used by my workflows. That is instead of a run my workflow would have a uses: ./.github/actions/perform-system. Note that the path to the action is relative to the root of the repository.

An action is effectively a directory with an action.yml file in it. That file has similar syntax to the workflow. There are three choices for actions: Javascript based, Docker based or “Composite”. Actions also can take inputs which are provided by with settings in a workflow that uses the action.

Since a composite action is just one that has several run commands I thought that would be the one to use, but as there did not seem to be a way to set an image to use for my action and it did not seem to be using the image I specified in my workflow this did not work out.

So I then went down the route of a docker action. I initially created a Dockerfile which used the clfoundation/sbcl and installed QuickLisp. I also created a entry point script which would take the inputs to my action (the system name, location of the system and operation to perform).

But… that didn’t work. After much trial and error I finally realized (and then found the documentation that explains what was happening) that my docker image being run wasn’t quite the same one I was building with my Dockerfile. Specifically things like the $HOME setting was different; and there was still the perplexing question of where my code was going to be in the image.

Because $HOME was different between building the image and running the image my plan of installing QuickLisp in the image was not going to work (QuickLisp’s installation adds things to your home directory). So that got pushed into my entry point script.

Even with these annoyances the resulting action is compact and easy to understand and the workflow was greatly simplified.

Next things to learn

I want to learn more about the docker images to see if I can improve how I set it up and if there is a reasonable way I can create a test-script for it so I can run it ‘locally’ for experimenting with it.

I also want to learn how to use this new action in other repositories. I think it may just be a matter of moving it into another public repository.

Comments