GitHub Action to Setup Common Lisp for CI

This is a Github Action to setup Common Lisp, Roswell and Qlot.

It is useful to call it before running tests or building docs for your Common Lisp libraries. Action encapsulates all steps necessary to make available Roswell and Qlot inside the Github CI.

What this action does for you?

A typical usage

Here is how a minimal GitHub Workflow might look like:

name: 'CI'

on:
  push:
    branches:
      - 'main'
      - 'master'
  pull_request:

jobs:
  tests:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        lisp:
          - sbcl-bin
          - ccl-bin
          
    env:
      LISP: ${{ matrix.lisp }}

    steps:
      - uses: actions/checkout@v1
      - uses: 40ants/setup-lisp@v1
        with:
          asdf-system: cl-info
      - uses: 40ants/run-tests@v2-beta
        with:
          asdf-system: cl-info

The part, corresponding to an action call is:

- uses: 40ants/setup-lisp@v1
  with:
    asdf-system: cl-info

If you remove with part, then action will skip the ASDF system installation.

Also, pay attention to the env section of the workflow. If you don't set up a LISP env variable, action will set default lisp implementation to sbcl:

env:
  LISP: ${{ matrix.lisp }}

The last step in this workflow runs tests for the specified ASDF system. It is documented here.

Overriding qlfile

By default, action will install the latest ASDF version. But sometimes you might want to fix an ASDF version. In such case, use asdf-version argument:

- uses: 40ants/setup-lisp@v1
  with:
    asdf-version: 3.3.4.18

Overriding qlfile

Sometimes you might want to generate content of qlfile depending on matrix parameters. For example with matrix like this one:

matrix:
  os:
    - ubuntu-latest
    - macos-latest
  quicklisp-dist:
    - quicklisp
    - ultralisp
  lisp:
    - sbcl-bin
    - ccl-bin
    - ecl

you might want to add an ultralisp source to the qlfile. Here is how this can be archived:

env:
  LISP: ${{ matrix.lisp }}
  OS: ${{ matrix.os }}
  QUICKLISP_DIST: ${{ matrix.quicklisp-dist }}

steps:
  - uses: actions/checkout@v1
  - uses: 40ants/setup-lisp@v1
    with:
      asdf-system: cl-info
      qlfile-template: |
        {% ifequal quicklisp_dist "ultralisp" %}
        dist ultralisp http://dist.ultralisp.org
        {% endifequal %}

Here we see a few important things.

  1. We put into the env var the type of the quicklisp distribution we want to our library to test against.

  2. We pass a multiline argument qlfile-template to the action.

  3. Template refers to quicklisp_dist to conditionally include a line into qlfile when quicklisp_dist == "ultralisp".

You can refer any environment variable inside the qlfile templater. Also note, it is using Djula markup, similar to Django and Jinja2.

Caching

Usually installing Roswell, a lisp implementation and dependencies take from 2 to 10 minutes. Multiply this to the number of matrix combinations and you'll get signifficant time.

To speed up build, you can use caching using a standad GitHub action actions/cache@v2.

To make caching work, add such sections into your workflow file:

- name: Grant All Perms to Make Cache Restoring Possible
  run: |
    sudo mkdir -p /usr/local/etc/roswell
    sudo chown "${USER}" /usr/local/etc/roswell
    # Here the ros binary will be restored:
    sudo chown "${USER}" /usr/local/bin
- name: Get Current Month
  id: current-month
  run: |
    echo "::set-output name=value::$(date -u "+%Y-%m")"
- name: Cache Roswell Setup
  id: cache
  uses: actions/cache@v2
  env:
    cache-name: cache-roswell
  with:
    path: |
      /usr/local/bin/ros
      ~/.cache/common-lisp/
      ~/.roswell
      /usr/local/etc/roswell
      .qlot
    key: "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-${{ runner.os }}-${{ hashFiles('qlfile.lock') }}"
- name: Restore Path To Cached Files
  run: |
    echo $HOME/.roswell/bin >> $GITHUB_PATH
    echo .qlot/bin >> $GITHUB_PATH
  if: steps.cache.outputs.cache-hit == 'true'
- uses: 40ants/setup-lisp@v1
  if: steps.cache.outputs.cache-hit != 'true'

There are two important lines here.

key: "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-${{ runner.os }}-${{ hashFiles('qlfile.lock') }}"

It controls when your cache will be matched. If you are using matrix, put all it's components into the key.

I also added a current month there, to make sure cache will be renewed at least monthly. This way a new Roswell, Qlot and ASDF will be used in a build.

Roadmap

Contribution

If you want to contribute to this system, join development at GitHub:

https://github.com/40ants/setup-lisp