Repository files navigation

40Ants-CI - Github Workflow Generator

This is a small utility, which can generate GitHub workflows for Common Lisp projects.

It generates workflow for running tests and building docs. These workflows use 40ants/run-tests and 40ants/build-docs actions and SBLint to check code for compilation errors.

40ANTS-CI ASDF System Details

Reasons to Use

  • This system hides all entrails related to caching.
  • Includes a few ready to use job types.
  • Custom job types can be defined and distributed as separate ASDF systems.
  • You don't have to write YAML anymore!

Quickstart

This system allows you to define workflows in the lisp code. The best way is to make these definitions a part of your ASDF system. This way 40ants-ci (12) will be able to automatically understand for which system it builds a workflow.

Each workflow consists of jobs and each job is a number of steps.

There are three predefine types of jobs and you can create your own. Predefined jobs allows to reuse steps in multiple CL libraries.

In next examples, I'll presume you are writing code in a file which is the part of the package inferred ASDF system EXAMPLE/CI. A file should have the following header:

(defpackage#:example/ci
(:use#:cl)
(:import-from#:40ants-ci/workflow
#:defworkflow)
(:import-from#:40ants-ci/jobs/linter)
(:import-from#:40ants-ci/jobs/run-tests)
(:import-from#:40ants-ci/jobs/docs))

Job Types

Autotag

This job is automates git tag placement on the commit where you have changed the ChangeLog.md.

This can be a useful to automate package deployment and releases. You update the changelog, a job pushes a new git tag and the next action triggers on this tag and build a release.

Or you if you publish your library at Quicklisp distribution, then you can change it's source type to the latest-github-tag to provide more stable releases to your users. This way you commits into master will be ignored until you change the changelog and git tag will be pushed. Here is an example how to setup this kind of quicklisp project source.

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag)))

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Linter

The simplest job type is linter. It loads a

(defworkflow linter
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)))

When you'll hit C-c C-c on this definition, it will generate .github/workflows/linter.yml with following content:

{
"name": "LINTER",
"on": {
"pull_request": null
},
"jobs": {
"linter": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Install SBLint",
"run": "qlot exec ros install cxxxr/sblint",
"shell": "bash"
},
{
"name": "Run Linter",
"run": "qlot exec sblint example.asd",
"shell": "bash"
}
]
}
}
}

Here you can see, a few steps in the job:

  1. Checkout the code.
  2. Install Roswell & Qlot using 40ants/setup-lisp action.
  3. Install SBLint.
  4. Run linter for example.asd.

Another interesting thing is that this workflow automatically uses ubuntu-latestOS, Quicklisp and sbcl-bin Lisp implementation. Later I'll show you how to redefine these settings.

class40ants-ci/jobs/linter:linter (lisp-job)

Critic

This job is similar to linter, but instead of SBLint it runs Lisp Critic.

Lisp Critic is a program which advices how to make you Common Lisp code more idiomatic, readable and performant. Also, sometimes it might catch logical errors in the code.

Here is how you can add this job type in your workflow:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/critic:critic)))

Also, you might combine this job together with others, for example, with linter:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)
(40ants-ci/jobs/critic:critic)))

and they will be executed in parallel. See docs on 40ants-ci/jobs/critic:critic function to learn about supported arguments.

Running Tests

Another interesting job type is 40ants-ci/jobs/run-tests:run-tests (12).

When using this job type, make sure, your system runs tests on (ASDF:TEST-SYSTEM :system-name) call and signals error if something went wrong.

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:jobs ((40ants-ci/jobs/run-tests:run-tests
:coveraget)))

Here I've added a few options to the workflow:

  • by-cron - sets a schedule.
  • on-push-to - defines a branch or branches to track.

It will generate .github/workflows/ci.yml with following content:

{
"name": "CI",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": null,
"schedule": [
{
"cron": "0 10 * * 1"
}
]
},
"jobs": {
"run-tests": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Run Tests",
"uses": "40ants/run-tests@v2",
"with": {
"asdf-system": "example",
"coveralls-token": "${{ secrets.github_token }}"
}
}
]
}
}
}

The result is similar to the workflow generated for Linter, but uses 40ants/setup-lisp action at the final step.

Also, I've passed an option :coverage t to the job. Thus coverage report will be uploaded to Coveralls.io automatically.

Defining a test Matrix

Lisp has many implementations and can be used on multiple platforms. Thus it is a good idea to test our software on many combinations of OS and lisp implementations. Workflow generator makes this very easy.

Here is an example of workflow definition with three dimentional matrix. It not only tests a library under different lisps and OS, but also checks if it works with the latest Quicklisp and Ultralisp distributions:

(defworkflow ci
:on-pull-requestt:jobs ((run-tests
:os ("ubuntu-latest""macos-latest")
:quicklisp ("quicklisp""ultralisp")
:lisp ("sbcl-bin""ccl-bin""allegro""clisp""cmucl")
:exclude (;; Seems allegro is does not support 64bit OSX.;; Unable to install it using Roswell:;; alisp is not executable. Missing 32bit glibc?
(:os"macos-latest":lisp"allegro")))))

Multiple jobs

Besides a build matrix, you might specify a multiple jobs of the same type, but with different parameters:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:lisp"sbcl-bin")
(run-tests
:lisp"ccl-bin")
(run-tests
:lisp"allegro")))

This will generate a workflow with three jobs: "run-tests", "run-tests-2" and "run-tests-3".

Meaningful names might be specified as well:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:name"test-on-sbcl":lisp"sbcl-bin")
(run-tests
:name"test-on-ccl":lisp"ccl-bin")
(run-tests
:name"test-on-allegro":lisp"allegro")))

Here is how these jobs will look like in the GitHub interface:

Building Docs

Third predefined job type is 40ants-ci/jobs/docs:build-docs (12). It uses 40ants/build-docs action and will work only if your ASDF system uses a documentation builder supported by 40ants/docs-builder.

To build docs on every push to master, just use this code:

(defworkflow docs
:on-push-to"master":jobs ((40ants-ci/jobs/docs:build-docs)))

It will generate .github/workflows/docs.yml with following content:

{
"name": "DOCS",
"on": {
"push": {
"branches": [
"master"
]
}
},
"jobs": {
"build-docs": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example",
"qlfile-template": ""
}
},
{
"name": "Build Docs",
"uses": "40ants/build-docs@v1",
"with": {
"asdf-system": "example"
}
}
]
}
}
}

Caching

To significantly speed up our tests, we can cache installed Roswell, Qlot and Common Lisp fasl files.

To accomplish this task, you don't need to dig into GitHub's docs anymore! Just add one line :cache t to your workflow definition:

(defworkflow docs
:on-push-to"master":cachet:jobs ((40ants-ci/jobs/docs:build-docs)))

Here is the diff of the generated workflow file. It shows steps, added automatically:

modified .github/workflows/docs.yml
@@ -20,13 +20,40 @@
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
+ {+ "name": "Grant All Perms to Make Cache Restoring Possible",+ "run": "sudo mkdir -p /usr/local/etc/roswell\n sudo chown \"${USER}\" /usr/local/etc/roswell\n # Here the ros binary will be restored:\n sudo chown \"${USER}\" /usr/local/bin",+ "shell": "bash"+ },+ {+ "name": "Get Current Month",+ "id": "current-month",+ "run": "echo \"::set-output name=value::$(date -u \"+%Y-%m\")\"",+ "shell": "bash"+ },+ {+ "name": "Cache Roswell Setup",+ "id": "cache",+ "uses": "actions/cache@v3",+ "with": {+ "path": "qlfile\n qlfile.lock\n /usr/local/bin/ros\n ~/.cache/common-lisp/\n ~/.roswell\n /usr/local/etc/roswell\n .qlot",+ "key": "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-ubuntu-latest-quicklisp-sbcl-bin-${{ hashFiles('qlfile.lock') }}"+ }+ },+ {+ "name": "Restore Path To Cached Files",+ "run": "echo $HOME/.roswell/bin >> $GITHUB_PATH\n echo .qlot/bin >> $GITHUB_PATH",+ "shell": "bash",+ "if": "steps.cache.outputs.cache-hit == 'true'"+ },
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "40ants-ci",
"qlfile-template": ""
- }+ },+ "if": "steps.cache.outputs.cache-hit != 'true'"
},
{

Adding env variables

You can specify additional environment variables on any level of the GitHub workflow: for workflow itself, for a job or for a step.

To specify env for workflow or a job, just add an ENV argument with alist or plist value like this:

(defworkflow release
:on-push-to"master":env (:github-token"${{ secrets.autotag_token }}")
:jobs ((40ants-ci/jobs/autotag:autotag)))

or as alist:

(defworkflow release
:on-push-to"master":env (("github_token"."${{ secrets.autotag_token }}"))
:jobs ((40ants-ci/jobs/autotag:autotag)))

or for the job itself:

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag
:env (:github-token"${{ secrets.autotag_token }}"))))

the same way it can be specified on a custom step:

(40ants-ci/steps/sh:sh "Custom env-var example""echo $CUSTOM_VAR":env (:custom-var"Hello world!"))

Note - environment variable names are always transformed to uppercase and dashes are replaced with underscores.

Running custom steps

Sometimes you might need to install custom system packages or do something before the job will finish. To accomplish these task you can provide custom steps using BEFORE-STEPS argument or AFTER-STEPS argument.

Here is an example where we are installing system package libunaq1-dev before running the testsuite:

(defparameter*required-steps*
(list (sh "Install libunac""sudo apt-get install -y libunac1-dev")))
(defworkflow ci
:on-pull-requestt:cachet:jobs ((run-tests
:steps-before*required-steps*:asdf-system"my-asdf-system")))

Details

TODO: I have to write a few chapters with details on additional job's parameters and a way how to create new job types.

But for now, I want to show a small example, how to define a workflow with a job which takes care about lisp installation and then calls a custom step:

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:cachet:jobs ((40ants-ci/jobs/lisp-job:lisp-job :name"check-ros-config":lisp"ccl-bin":steps ((40ants-ci/steps/sh:sh "Show Roswell Config""ros config")))))

Here we are using the class 40ants-ci/jobs/lisp-job:lisp-job which is base for most classes in this ASDF system and pass a custom 40ants-ci/steps/sh:sh (12) step to it. This step will be called after the repostory checkout and CCL-BIN lisp installation. so, thus when this step will run ros config command, it will output something like that:

asdf.version=3.3.5.3
ccl-bin.version=1.12.2
setup.time=3918000017
sbcl-bin.version=2.4.1
default.lisp=ccl-bin
Possible subcommands:
set
show

Pay attention to the NAME argument of 40ants-ci/jobs/lisp-job:lisp-job class. If you omit it, then default "lisp-job" name will be used.

API

40ANTS-CI

package40ants-ci

Functions

function40ants-ci:generate system &key path

Generates GitHub workflow for given ASDF system.

This function searches workflow definitions in all packages of the given ASDF system.

If PATH argument is not given, workflow files will be written to .github/workflow/ relarive to the SYSTEM.

40ANTS-CI/GITHUB

package40ants-ci/github

Generics

generic-function40ants-ci/github:generate obj path

generic-function40ants-ci/github:prepare-data obj

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

40ANTS-CI/JOBS/AUTOTAG

package40ants-ci/jobs/autotag

Classes

AUTOTAG

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Readers

reader40ants-ci/jobs/autotag:filename (autotag) (:filename = *default-filename*)

File where to search for version numbers.

reader40ants-ci/jobs/autotag:regex (autotag) (:regex = *default-regex*)

Regexp used to extract version numbers.

reader40ants-ci/jobs/autotag:tag-prefix (autotag) (:tag-prefix = *default-tag-prefix*)

Tag prefix.

reader40ants-ci/jobs/autotag:token-pattern (autotag) (:token-pattern = *default-token-pattern*)

Auth token pattern.

Functions

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

40ANTS-CI/JOBS/CRITIC

package40ants-ci/jobs/critic

Classes

CRITIC

class40ants-ci/jobs/critic:critic (lisp-job)

Readers

reader40ants-ci/jobs/critic:asdf-systems (critic) (:asdf-systems)

Critic can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/critic:ignore-critiques (critic) (:ignore-critiques)

A list strigns with names of critiques to ignore.

Functions

function40ants-ci/jobs/critic:critic &key asdf-systems asdf-version ignore-critiques env

Creates a job which will run Lisp Critic for given ASDF systems.

If argument ASDF-SYSTEMS is NIL, it will use ASDF system to which current lisp file is belong.

You may also provide ASDF-VERSION argument. It should be a string. By default, the latest ASDF version will be used.

40ANTS-CI/JOBS/DOCS

package40ants-ci/jobs/docs

Classes

BUILD-DOCS

class40ants-ci/jobs/docs:build-docs (lisp-job)

Builds documentation and uploads it to GitHub using "40ants/build-docs" github action.

Readers

reader40ants-ci/jobs/docs:error-on-warnings (build-docs) (:error-on-warnings = t)

Functions

function40ants-ci/jobs/docs:build-docs &rest args &key (error-on-warnings t) os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp asdf-system qlfile checkout-submodules dynamic-space-size

Creates a job of class build-docs.

40ANTS-CI/JOBS/JOB

package40ants-ci/jobs/job

Classes

JOB

class40ants-ci/jobs/job:job ()

Readers

reader40ants-ci/jobs/job:exclude (job) (:exclude = nil)

A list of plists denoting matrix combinations to be excluded.

reader40ants-ci/jobs/job:explicit-steps (job) (:steps = nil)

This slot holds steps given as a STEPS argument to a job constructor. Depending on a job class, it might add additional steps around these explicit steps.

reader40ants-ci/jobs/job:job-env (job) (:env = nil)

An alist of environment variables and their values to be added on job level. Values are evaluated in runtime.

reader40ants-ci/jobs/job:name (job) (:name)

If this name was not given in constructor, then name will be lowercased name of the job class.

reader40ants-ci/jobs/job:os (job) (:OS = "ubuntu-latest")

reader40ants-ci/jobs/job:permissions (job) (:permissions = nil)

A plist of permissions need for running the job.

These permissions will be bound to secrets.GITHUB_TOKEN variable. Use default-initargs to override permissions in subclasses:

(:default-initargs:permissions'(:content"write"))

reader40ants-ci/jobs/job:steps-after (job) (:steps-after = nil)

This slot holds steps given as a STEPS-AFTER argument to a job constructor. These steps will be appended to steps returned by the job class.

reader40ants-ci/jobs/job:steps-before (job) (:steps-before = nil)

This slot holds steps given as a STEPS-BEFORE argument to a job constructor. These steps will be prepended to steps returned by the job class.

Generics

generic-function40ants-ci/jobs/job:make-env job

generic-function40ants-ci/jobs/job:make-matrix job

generic-function40ants-ci/jobs/job:make-permissions job

Should return an alist with mapping from string to string where keys are scopes and values are permission names. Default method generates this alist from the plist of job's "permissions" slot.

generic-function40ants-ci/jobs/job:steps job

generic-function40ants-ci/jobs/job:use-matrix-p job

40ANTS-CI/JOBS/LINTER

package40ants-ci/jobs/linter

Classes

LINTER

class40ants-ci/jobs/linter:linter (lisp-job)

Readers

reader40ants-ci/jobs/linter:asdf-systems (linter) (:asdf-systems = nil)

Linter can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/linter:check-imports (linter) (:check-imports = nil)

Linter will check for missing or unused imports of package-inferred systems.

Functions

function40ants-ci/jobs/linter:linter &rest args &key asdf-systems check-imports os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp qlfile checkout-submodules dynamic-space-size

Creates a job which will run SBLint for given ASDF systems.

If no ASD files given, it will use all ASD files from the current ASDF system.

40ANTS-CI/JOBS/LISP-JOB

package40ants-ci/jobs/lisp-job

Classes

LISP-JOB

class40ants-ci/jobs/lisp-job:lisp-job (job)

This job checkouts the sources, installs Roswell and Qlot. Also, it caches results between runs.

Readers

reader40ants-ci/jobs/lisp-job:asdf-system (lisp-job) (:asdf-system = nil)

reader40ants-ci/jobs/lisp-job:asdf-version (lisp-job) (:asdf-version = nil)

ASDF version to use when setting up Lisp environment. If NIL, then the latest will be used.

reader40ants-ci/jobs/lisp-job:checkout-submodules (lisp-job) (:checkout-submodules = nil)

If this flag is true, then we will command actions/checkout action to checkout submodules.

reader40ants-ci/jobs/lisp-job:dynamic-space-size (lisp-job) (:dynamic-space-size = nil)

Dynamic space size for SBCL.

reader40ants-ci/jobs/lisp-job:lisp (lisp-job) (:LISP = "sbcl-bin")

reader40ants-ci/jobs/lisp-job:qlfile (lisp-job) (:qlfile = nil)

reader40ants-ci/jobs/lisp-job:qlot-version (lisp-job) (:qlot-version = nil)

Qlot version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

reader40ants-ci/jobs/lisp-job:quicklisp (lisp-job) (:QUICKLISP = "quicklisp")

reader40ants-ci/jobs/lisp-job:roswell-version (lisp-job) (:roswell-version = nil)

Roswell version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

40ANTS-CI/JOBS/RUN-TESTS

package40ants-ci/jobs/run-tests

Classes

RUN-TESTS

class40ants-ci/jobs/run-tests:run-tests (lisp-job)

This job test runs tests for a given ASDF system.

Readers

reader40ants-ci/jobs/run-tests:coverage (run-tests) (:coverage = nil)

reader40ants-ci/jobs/run-tests:custom (run-tests) (:custom = nil)

Functions

function40ants-ci/jobs/run-tests:run-tests &rest rest &key coverage custom os permissions steps steps-before steps-after env roswell-version asdf-version qlot-version lisp exclude qlfile quicklisp asdf-system checkout-submodules dynamic-space-size

Creates a job step of class run-tests.

40ANTS-CI/STEPS/ACTION

package40ants-ci/steps/action

Classes

ACTION

class40ants-ci/steps/action:action (step)

Readers

reader40ants-ci/steps/action:action-args (action) (:args)

A plist to be passed as "with" dictionary to the action.

reader40ants-ci/steps/action:uses (action) (:uses)

Functions

function40ants-ci/steps/action:action name uses &rest args &key id if env &allow-other-keys

40ANTS-CI/STEPS/SH

package40ants-ci/steps/sh

Classes

SH

class40ants-ci/steps/sh:sh (step)

Readers

reader40ants-ci/steps/sh:command (sh) (:command)

reader40ants-ci/steps/sh:shell (sh) (:shell = *default-shell*)

Functions

function40ants-ci/steps/sh:sh name command &key id if (shell *default-shell*) env

Macros

macro40ants-ci/steps/sh:sections &body body

Returns a string with a bash script where some parts are grouped.

In this example we have 3 sections:

(sections
("Help Argument""qlot exec cl-info --help")
("Version Argument""qlot exec cl-info --version")
("Lisp Systems Info""qlot exec cl-info""qlot exec cl-info cl-info defmain"))

It will be compiled into:

echo ::group::Help Argument
qlot exec cl-info --help
echo ::endgroup::
echo ::group::Version Argument
qlot exec cl-info --version
echo ::endgroup::
echo ::group::Lisp Systems Info
qlot exec cl-info
qlot exec cl-info cl-info defmain
echo ::endgroup::

40ANTS-CI/STEPS/STEP

package40ants-ci/steps/step

Classes

STEP

class40ants-ci/steps/step:step ()

Readers

reader40ants-ci/steps/step:env (step) (:env = nil)

An alist of environment variables.

reader40ants-ci/steps/step:step-id (step) (:id = nil)

reader40ants-ci/steps/step:step-if (step) (:if = nil)

reader40ants-ci/steps/step:step-name (step) (:name = nil)

40ANTS-CI/UTILS

package40ants-ci/utils

Generics

generic-function40ants-ci/utils:system-packages system

Returns a list of packages created by ASDF system.

Default implementation returns a package having the same name as a system and all packages matched to package-inferred subsystems:

CL-USER> (docs-builder/utils:system-packages :docs-builder)
(#<PACKAGE "DOCS-BUILDER">
#<PACKAGE "DOCS-BUILDER/UTILS">
#<PACKAGE "DOCS-BUILDER/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/GENEVA/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/GUESSER">
#<PACKAGE "DOCS-BUILDER/DOCS">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/BUILDER">)

Functions

function40ants-ci/utils:alistp list

Test wheather LIST argument is a properly formed alist.

In this library, alist has always a string as a key. Because we need them to have this form to serialize to JSON propertly.

(alistp '(("cron" . "0 10 * * 1"))) -> T (alistp '((("cron" . "0 10 * * 1")))) -> NIL

function40ants-ci/utils:current-system-name

function40ants-ci/utils:dedent text

Removes common leading whitespace from each string.

A few examples:

(dedent "Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "
Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD.")
"This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD."

function40ants-ci/utils:ensure-list-of-plists data

function40ants-ci/utils:ensure-primary-system system

function40ants-ci/utils:make-github-workflows-path system

function40ants-ci/utils:plist-to-alist plist &key (string-keys t) (lowercase t)

Make an alist from a plist PLIST.

By default, transforms keys to lowercased strings

function40ants-ci/utils:plistp list

Test wheather LIST is a properly formed plist.

function40ants-ci/utils:single list

Test wheather LIST contains exactly 1 element.

function40ants-ci/utils:to-json data

40ANTS-CI/VARS

package40ants-ci/vars

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

variable40ants-ci/vars:*use-cache* nil

Workflow will set this variable when preparing the data or YAML generation.

40ANTS-CI/WORKFLOW

package40ants-ci/workflow

Classes

WORKFLOW

class40ants-ci/workflow:workflow ()

Readers

reader40ants-ci/workflow:by-cron (workflow) (:by-cron = "0 10 * * 1")

reader40ants-ci/workflow:cache-p (workflow) (:cache = t)

reader40ants-ci/workflow:jobs (workflow) (:jobs = nil)

reader40ants-ci/workflow:name (workflow) (:name)

reader40ants-ci/workflow:on-pull-request (workflow) (:on-pull-request = t)

reader40ants-ci/workflow:on-push-to (workflow) (:ON-PUSH-TO = "master")

reader40ants-ci/workflow:workflow-env (workflow) (:env = nil)

An alist of environment variables and their values to be added on workflow level. Values are evaluated in runtime.

Macros

macro40ants-ci/workflow:defworkflow name &key on-push-to by-cron on-pull-request cache env jobs

[generated by 40ANTS-DOC]

About

Highly opionated Github Actions workflow builder for Common Lisp projects.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

40Ants-CI - Github Workflow Generator

This is a small utility, which can generate GitHub workflows for Common Lisp projects.

It generates workflow for running tests and building docs. These workflows use 40ants/run-tests and 40ants/build-docs actions and SBLint to check code for compilation errors.

40ANTS-CI ASDF System Details

Reasons to Use

  • This system hides all entrails related to caching.
  • Includes a few ready to use job types.
  • Custom job types can be defined and distributed as separate ASDF systems.
  • You don't have to write YAML anymore!

Quickstart

This system allows you to define workflows in the lisp code. The best way is to make these definitions a part of your ASDF system. This way 40ants-ci (12) will be able to automatically understand for which system it builds a workflow.

Each workflow consists of jobs and each job is a number of steps.

There are three predefine types of jobs and you can create your own. Predefined jobs allows to reuse steps in multiple CL libraries.

In next examples, I'll presume you are writing code in a file which is the part of the package inferred ASDF system EXAMPLE/CI. A file should have the following header:

(defpackage#:example/ci
(:use#:cl)
(:import-from#:40ants-ci/workflow
#:defworkflow)
(:import-from#:40ants-ci/jobs/linter)
(:import-from#:40ants-ci/jobs/run-tests)
(:import-from#:40ants-ci/jobs/docs))

Job Types

Autotag

This job is automates git tag placement on the commit where you have changed the ChangeLog.md.

This can be a useful to automate package deployment and releases. You update the changelog, a job pushes a new git tag and the next action triggers on this tag and build a release.

Or you if you publish your library at Quicklisp distribution, then you can change it's source type to the latest-github-tag to provide more stable releases to your users. This way you commits into master will be ignored until you change the changelog and git tag will be pushed. Here is an example how to setup this kind of quicklisp project source.

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag)))

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Linter

The simplest job type is linter. It loads a

(defworkflow linter
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)))

When you'll hit C-c C-c on this definition, it will generate .github/workflows/linter.yml with following content:

{
"name": "LINTER",
"on": {
"pull_request": null
},
"jobs": {
"linter": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Install SBLint",
"run": "qlot exec ros install cxxxr/sblint",
"shell": "bash"
},
{
"name": "Run Linter",
"run": "qlot exec sblint example.asd",
"shell": "bash"
}
]
}
}
}

Here you can see, a few steps in the job:

  1. Checkout the code.
  2. Install Roswell & Qlot using 40ants/setup-lisp action.
  3. Install SBLint.
  4. Run linter for example.asd.

Another interesting thing is that this workflow automatically uses ubuntu-latestOS, Quicklisp and sbcl-bin Lisp implementation. Later I'll show you how to redefine these settings.

class40ants-ci/jobs/linter:linter (lisp-job)

Critic

This job is similar to linter, but instead of SBLint it runs Lisp Critic.

Lisp Critic is a program which advices how to make you Common Lisp code more idiomatic, readable and performant. Also, sometimes it might catch logical errors in the code.

Here is how you can add this job type in your workflow:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/critic:critic)))

Also, you might combine this job together with others, for example, with linter:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)
(40ants-ci/jobs/critic:critic)))

and they will be executed in parallel. See docs on 40ants-ci/jobs/critic:critic function to learn about supported arguments.

Running Tests

Another interesting job type is 40ants-ci/jobs/run-tests:run-tests (12).

When using this job type, make sure, your system runs tests on (ASDF:TEST-SYSTEM :system-name) call and signals error if something went wrong.

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:jobs ((40ants-ci/jobs/run-tests:run-tests
:coveraget)))

Here I've added a few options to the workflow:

  • by-cron - sets a schedule.
  • on-push-to - defines a branch or branches to track.

It will generate .github/workflows/ci.yml with following content:

{
"name": "CI",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": null,
"schedule": [
{
"cron": "0 10 * * 1"
}
]
},
"jobs": {
"run-tests": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Run Tests",
"uses": "40ants/run-tests@v2",
"with": {
"asdf-system": "example",
"coveralls-token": "${{ secrets.github_token }}"
}
}
]
}
}
}

The result is similar to the workflow generated for Linter, but uses 40ants/setup-lisp action at the final step.

Also, I've passed an option :coverage t to the job. Thus coverage report will be uploaded to Coveralls.io automatically.

Defining a test Matrix

Lisp has many implementations and can be used on multiple platforms. Thus it is a good idea to test our software on many combinations of OS and lisp implementations. Workflow generator makes this very easy.

Here is an example of workflow definition with three dimentional matrix. It not only tests a library under different lisps and OS, but also checks if it works with the latest Quicklisp and Ultralisp distributions:

(defworkflow ci
:on-pull-requestt:jobs ((run-tests
:os ("ubuntu-latest""macos-latest")
:quicklisp ("quicklisp""ultralisp")
:lisp ("sbcl-bin""ccl-bin""allegro""clisp""cmucl")
:exclude (;; Seems allegro is does not support 64bit OSX.;; Unable to install it using Roswell:;; alisp is not executable. Missing 32bit glibc?
(:os"macos-latest":lisp"allegro")))))

Multiple jobs

Besides a build matrix, you might specify a multiple jobs of the same type, but with different parameters:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:lisp"sbcl-bin")
(run-tests
:lisp"ccl-bin")
(run-tests
:lisp"allegro")))

This will generate a workflow with three jobs: "run-tests", "run-tests-2" and "run-tests-3".

Meaningful names might be specified as well:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:name"test-on-sbcl":lisp"sbcl-bin")
(run-tests
:name"test-on-ccl":lisp"ccl-bin")
(run-tests
:name"test-on-allegro":lisp"allegro")))

Here is how these jobs will look like in the GitHub interface:

Building Docs

Third predefined job type is 40ants-ci/jobs/docs:build-docs (12). It uses 40ants/build-docs action and will work only if your ASDF system uses a documentation builder supported by 40ants/docs-builder.

To build docs on every push to master, just use this code:

(defworkflow docs
:on-push-to"master":jobs ((40ants-ci/jobs/docs:build-docs)))

It will generate .github/workflows/docs.yml with following content:

{
"name": "DOCS",
"on": {
"push": {
"branches": [
"master"
]
}
},
"jobs": {
"build-docs": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example",
"qlfile-template": ""
}
},
{
"name": "Build Docs",
"uses": "40ants/build-docs@v1",
"with": {
"asdf-system": "example"
}
}
]
}
}
}

Caching

To significantly speed up our tests, we can cache installed Roswell, Qlot and Common Lisp fasl files.

To accomplish this task, you don't need to dig into GitHub's docs anymore! Just add one line :cache t to your workflow definition:

(defworkflow docs
:on-push-to"master":cachet:jobs ((40ants-ci/jobs/docs:build-docs)))

Here is the diff of the generated workflow file. It shows steps, added automatically:

modified .github/workflows/docs.yml
@@ -20,13 +20,40 @@
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
+ {+ "name": "Grant All Perms to Make Cache Restoring Possible",+ "run": "sudo mkdir -p /usr/local/etc/roswell\n sudo chown \"${USER}\" /usr/local/etc/roswell\n # Here the ros binary will be restored:\n sudo chown \"${USER}\" /usr/local/bin",+ "shell": "bash"+ },+ {+ "name": "Get Current Month",+ "id": "current-month",+ "run": "echo \"::set-output name=value::$(date -u \"+%Y-%m\")\"",+ "shell": "bash"+ },+ {+ "name": "Cache Roswell Setup",+ "id": "cache",+ "uses": "actions/cache@v3",+ "with": {+ "path": "qlfile\n qlfile.lock\n /usr/local/bin/ros\n ~/.cache/common-lisp/\n ~/.roswell\n /usr/local/etc/roswell\n .qlot",+ "key": "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-ubuntu-latest-quicklisp-sbcl-bin-${{ hashFiles('qlfile.lock') }}"+ }+ },+ {+ "name": "Restore Path To Cached Files",+ "run": "echo $HOME/.roswell/bin >> $GITHUB_PATH\n echo .qlot/bin >> $GITHUB_PATH",+ "shell": "bash",+ "if": "steps.cache.outputs.cache-hit == 'true'"+ },
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "40ants-ci",
"qlfile-template": ""
- }+ },+ "if": "steps.cache.outputs.cache-hit != 'true'"
},
{

Adding env variables

You can specify additional environment variables on any level of the GitHub workflow: for workflow itself, for a job or for a step.

To specify env for workflow or a job, just add an ENV argument with alist or plist value like this:

(defworkflow release
:on-push-to"master":env (:github-token"${{ secrets.autotag_token }}")
:jobs ((40ants-ci/jobs/autotag:autotag)))

or as alist:

(defworkflow release
:on-push-to"master":env (("github_token"."${{ secrets.autotag_token }}"))
:jobs ((40ants-ci/jobs/autotag:autotag)))

or for the job itself:

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag
:env (:github-token"${{ secrets.autotag_token }}"))))

the same way it can be specified on a custom step:

(40ants-ci/steps/sh:sh "Custom env-var example""echo $CUSTOM_VAR":env (:custom-var"Hello world!"))

Note - environment variable names are always transformed to uppercase and dashes are replaced with underscores.

Running custom steps

Sometimes you might need to install custom system packages or do something before the job will finish. To accomplish these task you can provide custom steps using BEFORE-STEPS argument or AFTER-STEPS argument.

Here is an example where we are installing system package libunaq1-dev before running the testsuite:

(defparameter*required-steps*
(list (sh "Install libunac""sudo apt-get install -y libunac1-dev")))
(defworkflow ci
:on-pull-requestt:cachet:jobs ((run-tests
:steps-before*required-steps*:asdf-system"my-asdf-system")))

Details

TODO: I have to write a few chapters with details on additional job's parameters and a way how to create new job types.

But for now, I want to show a small example, how to define a workflow with a job which takes care about lisp installation and then calls a custom step:

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:cachet:jobs ((40ants-ci/jobs/lisp-job:lisp-job :name"check-ros-config":lisp"ccl-bin":steps ((40ants-ci/steps/sh:sh "Show Roswell Config""ros config")))))

Here we are using the class 40ants-ci/jobs/lisp-job:lisp-job which is base for most classes in this ASDF system and pass a custom 40ants-ci/steps/sh:sh (12) step to it. This step will be called after the repostory checkout and CCL-BIN lisp installation. so, thus when this step will run ros config command, it will output something like that:

asdf.version=3.3.5.3
ccl-bin.version=1.12.2
setup.time=3918000017
sbcl-bin.version=2.4.1
default.lisp=ccl-bin
Possible subcommands:
set
show

Pay attention to the NAME argument of 40ants-ci/jobs/lisp-job:lisp-job class. If you omit it, then default "lisp-job" name will be used.

API

40ANTS-CI

package40ants-ci

Functions

function40ants-ci:generate system &key path

Generates GitHub workflow for given ASDF system.

This function searches workflow definitions in all packages of the given ASDF system.

If PATH argument is not given, workflow files will be written to .github/workflow/ relarive to the SYSTEM.

40ANTS-CI/GITHUB

package40ants-ci/github

Generics

generic-function40ants-ci/github:generate obj path

generic-function40ants-ci/github:prepare-data obj

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

40ANTS-CI/JOBS/AUTOTAG

package40ants-ci/jobs/autotag

Classes

AUTOTAG

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Readers

reader40ants-ci/jobs/autotag:filename (autotag) (:filename = *default-filename*)

File where to search for version numbers.

reader40ants-ci/jobs/autotag:regex (autotag) (:regex = *default-regex*)

Regexp used to extract version numbers.

reader40ants-ci/jobs/autotag:tag-prefix (autotag) (:tag-prefix = *default-tag-prefix*)

Tag prefix.

reader40ants-ci/jobs/autotag:token-pattern (autotag) (:token-pattern = *default-token-pattern*)

Auth token pattern.

Functions

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

40ANTS-CI/JOBS/CRITIC

package40ants-ci/jobs/critic

Classes

CRITIC

class40ants-ci/jobs/critic:critic (lisp-job)

Readers

reader40ants-ci/jobs/critic:asdf-systems (critic) (:asdf-systems)

Critic can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/critic:ignore-critiques (critic) (:ignore-critiques)

A list strigns with names of critiques to ignore.

Functions

function40ants-ci/jobs/critic:critic &key asdf-systems asdf-version ignore-critiques env

Creates a job which will run Lisp Critic for given ASDF systems.

If argument ASDF-SYSTEMS is NIL, it will use ASDF system to which current lisp file is belong.

You may also provide ASDF-VERSION argument. It should be a string. By default, the latest ASDF version will be used.

40ANTS-CI/JOBS/DOCS

package40ants-ci/jobs/docs

Classes

BUILD-DOCS

class40ants-ci/jobs/docs:build-docs (lisp-job)

Builds documentation and uploads it to GitHub using "40ants/build-docs" github action.

Readers

reader40ants-ci/jobs/docs:error-on-warnings (build-docs) (:error-on-warnings = t)

Functions

function40ants-ci/jobs/docs:build-docs &rest args &key (error-on-warnings t) os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp asdf-system qlfile checkout-submodules dynamic-space-size

Creates a job of class build-docs.

40ANTS-CI/JOBS/JOB

package40ants-ci/jobs/job

Classes

JOB

class40ants-ci/jobs/job:job ()

Readers

reader40ants-ci/jobs/job:exclude (job) (:exclude = nil)

A list of plists denoting matrix combinations to be excluded.

reader40ants-ci/jobs/job:explicit-steps (job) (:steps = nil)

This slot holds steps given as a STEPS argument to a job constructor. Depending on a job class, it might add additional steps around these explicit steps.

reader40ants-ci/jobs/job:job-env (job) (:env = nil)

An alist of environment variables and their values to be added on job level. Values are evaluated in runtime.

reader40ants-ci/jobs/job:name (job) (:name)

If this name was not given in constructor, then name will be lowercased name of the job class.

reader40ants-ci/jobs/job:os (job) (:OS = "ubuntu-latest")

reader40ants-ci/jobs/job:permissions (job) (:permissions = nil)

A plist of permissions need for running the job.

These permissions will be bound to secrets.GITHUB_TOKEN variable. Use default-initargs to override permissions in subclasses:

(:default-initargs:permissions'(:content"write"))

reader40ants-ci/jobs/job:steps-after (job) (:steps-after = nil)

This slot holds steps given as a STEPS-AFTER argument to a job constructor. These steps will be appended to steps returned by the job class.

reader40ants-ci/jobs/job:steps-before (job) (:steps-before = nil)

This slot holds steps given as a STEPS-BEFORE argument to a job constructor. These steps will be prepended to steps returned by the job class.

Generics

generic-function40ants-ci/jobs/job:make-env job

generic-function40ants-ci/jobs/job:make-matrix job

generic-function40ants-ci/jobs/job:make-permissions job

Should return an alist with mapping from string to string where keys are scopes and values are permission names. Default method generates this alist from the plist of job's "permissions" slot.

generic-function40ants-ci/jobs/job:steps job

generic-function40ants-ci/jobs/job:use-matrix-p job

40ANTS-CI/JOBS/LINTER

package40ants-ci/jobs/linter

Classes

LINTER

class40ants-ci/jobs/linter:linter (lisp-job)

Readers

reader40ants-ci/jobs/linter:asdf-systems (linter) (:asdf-systems = nil)

Linter can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/linter:check-imports (linter) (:check-imports = nil)

Linter will check for missing or unused imports of package-inferred systems.

Functions

function40ants-ci/jobs/linter:linter &rest args &key asdf-systems check-imports os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp qlfile checkout-submodules dynamic-space-size

Creates a job which will run SBLint for given ASDF systems.

If no ASD files given, it will use all ASD files from the current ASDF system.

40ANTS-CI/JOBS/LISP-JOB

package40ants-ci/jobs/lisp-job

Classes

LISP-JOB

class40ants-ci/jobs/lisp-job:lisp-job (job)

This job checkouts the sources, installs Roswell and Qlot. Also, it caches results between runs.

Readers

reader40ants-ci/jobs/lisp-job:asdf-system (lisp-job) (:asdf-system = nil)

reader40ants-ci/jobs/lisp-job:asdf-version (lisp-job) (:asdf-version = nil)

ASDF version to use when setting up Lisp environment. If NIL, then the latest will be used.

reader40ants-ci/jobs/lisp-job:checkout-submodules (lisp-job) (:checkout-submodules = nil)

If this flag is true, then we will command actions/checkout action to checkout submodules.

reader40ants-ci/jobs/lisp-job:dynamic-space-size (lisp-job) (:dynamic-space-size = nil)

Dynamic space size for SBCL.

reader40ants-ci/jobs/lisp-job:lisp (lisp-job) (:LISP = "sbcl-bin")

reader40ants-ci/jobs/lisp-job:qlfile (lisp-job) (:qlfile = nil)

reader40ants-ci/jobs/lisp-job:qlot-version (lisp-job) (:qlot-version = nil)

Qlot version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

reader40ants-ci/jobs/lisp-job:quicklisp (lisp-job) (:QUICKLISP = "quicklisp")

reader40ants-ci/jobs/lisp-job:roswell-version (lisp-job) (:roswell-version = nil)

Roswell version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

40ANTS-CI/JOBS/RUN-TESTS

package40ants-ci/jobs/run-tests

Classes

RUN-TESTS

class40ants-ci/jobs/run-tests:run-tests (lisp-job)

This job test runs tests for a given ASDF system.

Readers

reader40ants-ci/jobs/run-tests:coverage (run-tests) (:coverage = nil)

reader40ants-ci/jobs/run-tests:custom (run-tests) (:custom = nil)

Functions

function40ants-ci/jobs/run-tests:run-tests &rest rest &key coverage custom os permissions steps steps-before steps-after env roswell-version asdf-version qlot-version lisp exclude qlfile quicklisp asdf-system checkout-submodules dynamic-space-size

Creates a job step of class run-tests.

40ANTS-CI/STEPS/ACTION

package40ants-ci/steps/action

Classes

ACTION

class40ants-ci/steps/action:action (step)

Readers

reader40ants-ci/steps/action:action-args (action) (:args)

A plist to be passed as "with" dictionary to the action.

reader40ants-ci/steps/action:uses (action) (:uses)

Functions

function40ants-ci/steps/action:action name uses &rest args &key id if env &allow-other-keys

40ANTS-CI/STEPS/SH

package40ants-ci/steps/sh

Classes

SH

class40ants-ci/steps/sh:sh (step)

Readers

reader40ants-ci/steps/sh:command (sh) (:command)

reader40ants-ci/steps/sh:shell (sh) (:shell = *default-shell*)

Functions

function40ants-ci/steps/sh:sh name command &key id if (shell *default-shell*) env

Macros

macro40ants-ci/steps/sh:sections &body body

Returns a string with a bash script where some parts are grouped.

In this example we have 3 sections:

(sections
("Help Argument""qlot exec cl-info --help")
("Version Argument""qlot exec cl-info --version")
("Lisp Systems Info""qlot exec cl-info""qlot exec cl-info cl-info defmain"))

It will be compiled into:

echo ::group::Help Argument
qlot exec cl-info --help
echo ::endgroup::
echo ::group::Version Argument
qlot exec cl-info --version
echo ::endgroup::
echo ::group::Lisp Systems Info
qlot exec cl-info
qlot exec cl-info cl-info defmain
echo ::endgroup::

40ANTS-CI/STEPS/STEP

package40ants-ci/steps/step

Classes

STEP

class40ants-ci/steps/step:step ()

Readers

reader40ants-ci/steps/step:env (step) (:env = nil)

An alist of environment variables.

reader40ants-ci/steps/step:step-id (step) (:id = nil)

reader40ants-ci/steps/step:step-if (step) (:if = nil)

reader40ants-ci/steps/step:step-name (step) (:name = nil)

40ANTS-CI/UTILS

package40ants-ci/utils

Generics

generic-function40ants-ci/utils:system-packages system

Returns a list of packages created by ASDF system.

Default implementation returns a package having the same name as a system and all packages matched to package-inferred subsystems:

CL-USER> (docs-builder/utils:system-packages :docs-builder)
(#<PACKAGE "DOCS-BUILDER">
#<PACKAGE "DOCS-BUILDER/UTILS">
#<PACKAGE "DOCS-BUILDER/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/GENEVA/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/GUESSER">
#<PACKAGE "DOCS-BUILDER/DOCS">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/BUILDER">)

Functions

function40ants-ci/utils:alistp list

Test wheather LIST argument is a properly formed alist.

In this library, alist has always a string as a key. Because we need them to have this form to serialize to JSON propertly.

(alistp '(("cron" . "0 10 * * 1"))) -> T (alistp '((("cron" . "0 10 * * 1")))) -> NIL

function40ants-ci/utils:current-system-name

function40ants-ci/utils:dedent text

Removes common leading whitespace from each string.

A few examples:

(dedent "Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "
Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD.")
"This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD."

function40ants-ci/utils:ensure-list-of-plists data

function40ants-ci/utils:ensure-primary-system system

function40ants-ci/utils:make-github-workflows-path system

function40ants-ci/utils:plist-to-alist plist &key (string-keys t) (lowercase t)

Make an alist from a plist PLIST.

By default, transforms keys to lowercased strings

function40ants-ci/utils:plistp list

Test wheather LIST is a properly formed plist.

function40ants-ci/utils:single list

Test wheather LIST contains exactly 1 element.

function40ants-ci/utils:to-json data

40ANTS-CI/VARS

package40ants-ci/vars

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

variable40ants-ci/vars:*use-cache* nil

Workflow will set this variable when preparing the data or YAML generation.

40ANTS-CI/WORKFLOW

package40ants-ci/workflow

Classes

WORKFLOW

class40ants-ci/workflow:workflow ()

Readers

reader40ants-ci/workflow:by-cron (workflow) (:by-cron = "0 10 * * 1")

reader40ants-ci/workflow:cache-p (workflow) (:cache = t)

reader40ants-ci/workflow:jobs (workflow) (:jobs = nil)

reader40ants-ci/workflow:name (workflow) (:name)

reader40ants-ci/workflow:on-pull-request (workflow) (:on-pull-request = t)

reader40ants-ci/workflow:on-push-to (workflow) (:ON-PUSH-TO = "master")

reader40ants-ci/workflow:workflow-env (workflow) (:env = nil)

An alist of environment variables and their values to be added on workflow level. Values are evaluated in runtime.

Macros

macro40ants-ci/workflow:defworkflow name &key on-push-to by-cron on-pull-request cache env jobs

[generated by 40ANTS-DOC]

About

Highly opionated Github Actions workflow builder for Common Lisp projects.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

40Ants-CI - Github Workflow Generator

This is a small utility, which can generate GitHub workflows for Common Lisp projects.

It generates workflow for running tests and building docs. These workflows use 40ants/run-tests and 40ants/build-docs actions and SBLint to check code for compilation errors.

40ANTS-CI ASDF System Details

Reasons to Use

  • This system hides all entrails related to caching.
  • Includes a few ready to use job types.
  • Custom job types can be defined and distributed as separate ASDF systems.
  • You don't have to write YAML anymore!

Quickstart

This system allows you to define workflows in the lisp code. The best way is to make these definitions a part of your ASDF system. This way 40ants-ci (12) will be able to automatically understand for which system it builds a workflow.

Each workflow consists of jobs and each job is a number of steps.

There are three predefine types of jobs and you can create your own. Predefined jobs allows to reuse steps in multiple CL libraries.

In next examples, I'll presume you are writing code in a file which is the part of the package inferred ASDF system EXAMPLE/CI. A file should have the following header:

(defpackage#:example/ci
(:use#:cl)
(:import-from#:40ants-ci/workflow
#:defworkflow)
(:import-from#:40ants-ci/jobs/linter)
(:import-from#:40ants-ci/jobs/run-tests)
(:import-from#:40ants-ci/jobs/docs))

Job Types

Autotag

This job is automates git tag placement on the commit where you have changed the ChangeLog.md.

This can be a useful to automate package deployment and releases. You update the changelog, a job pushes a new git tag and the next action triggers on this tag and build a release.

Or you if you publish your library at Quicklisp distribution, then you can change it's source type to the latest-github-tag to provide more stable releases to your users. This way you commits into master will be ignored until you change the changelog and git tag will be pushed. Here is an example how to setup this kind of quicklisp project source.

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag)))

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Linter

The simplest job type is linter. It loads a

(defworkflow linter
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)))

When you'll hit C-c C-c on this definition, it will generate .github/workflows/linter.yml with following content:

{
"name": "LINTER",
"on": {
"pull_request": null
},
"jobs": {
"linter": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Install SBLint",
"run": "qlot exec ros install cxxxr/sblint",
"shell": "bash"
},
{
"name": "Run Linter",
"run": "qlot exec sblint example.asd",
"shell": "bash"
}
]
}
}
}

Here you can see, a few steps in the job:

  1. Checkout the code.
  2. Install Roswell & Qlot using 40ants/setup-lisp action.
  3. Install SBLint.
  4. Run linter for example.asd.

Another interesting thing is that this workflow automatically uses ubuntu-latestOS, Quicklisp and sbcl-bin Lisp implementation. Later I'll show you how to redefine these settings.

class40ants-ci/jobs/linter:linter (lisp-job)

Critic

This job is similar to linter, but instead of SBLint it runs Lisp Critic.

Lisp Critic is a program which advices how to make you Common Lisp code more idiomatic, readable and performant. Also, sometimes it might catch logical errors in the code.

Here is how you can add this job type in your workflow:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/critic:critic)))

Also, you might combine this job together with others, for example, with linter:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)
(40ants-ci/jobs/critic:critic)))

and they will be executed in parallel. See docs on 40ants-ci/jobs/critic:critic function to learn about supported arguments.

Running Tests

Another interesting job type is 40ants-ci/jobs/run-tests:run-tests (12).

When using this job type, make sure, your system runs tests on (ASDF:TEST-SYSTEM :system-name) call and signals error if something went wrong.

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:jobs ((40ants-ci/jobs/run-tests:run-tests
:coveraget)))

Here I've added a few options to the workflow:

  • by-cron - sets a schedule.
  • on-push-to - defines a branch or branches to track.

It will generate .github/workflows/ci.yml with following content:

{
"name": "CI",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": null,
"schedule": [
{
"cron": "0 10 * * 1"
}
]
},
"jobs": {
"run-tests": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Run Tests",
"uses": "40ants/run-tests@v2",
"with": {
"asdf-system": "example",
"coveralls-token": "${{ secrets.github_token }}"
}
}
]
}
}
}

The result is similar to the workflow generated for Linter, but uses 40ants/setup-lisp action at the final step.

Also, I've passed an option :coverage t to the job. Thus coverage report will be uploaded to Coveralls.io automatically.

Defining a test Matrix

Lisp has many implementations and can be used on multiple platforms. Thus it is a good idea to test our software on many combinations of OS and lisp implementations. Workflow generator makes this very easy.

Here is an example of workflow definition with three dimentional matrix. It not only tests a library under different lisps and OS, but also checks if it works with the latest Quicklisp and Ultralisp distributions:

(defworkflow ci
:on-pull-requestt:jobs ((run-tests
:os ("ubuntu-latest""macos-latest")
:quicklisp ("quicklisp""ultralisp")
:lisp ("sbcl-bin""ccl-bin""allegro""clisp""cmucl")
:exclude (;; Seems allegro is does not support 64bit OSX.;; Unable to install it using Roswell:;; alisp is not executable. Missing 32bit glibc?
(:os"macos-latest":lisp"allegro")))))

Multiple jobs

Besides a build matrix, you might specify a multiple jobs of the same type, but with different parameters:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:lisp"sbcl-bin")
(run-tests
:lisp"ccl-bin")
(run-tests
:lisp"allegro")))

This will generate a workflow with three jobs: "run-tests", "run-tests-2" and "run-tests-3".

Meaningful names might be specified as well:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:name"test-on-sbcl":lisp"sbcl-bin")
(run-tests
:name"test-on-ccl":lisp"ccl-bin")
(run-tests
:name"test-on-allegro":lisp"allegro")))

Here is how these jobs will look like in the GitHub interface:

Building Docs

Third predefined job type is 40ants-ci/jobs/docs:build-docs (12). It uses 40ants/build-docs action and will work only if your ASDF system uses a documentation builder supported by 40ants/docs-builder.

To build docs on every push to master, just use this code:

(defworkflow docs
:on-push-to"master":jobs ((40ants-ci/jobs/docs:build-docs)))

It will generate .github/workflows/docs.yml with following content:

{
"name": "DOCS",
"on": {
"push": {
"branches": [
"master"
]
}
},
"jobs": {
"build-docs": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example",
"qlfile-template": ""
}
},
{
"name": "Build Docs",
"uses": "40ants/build-docs@v1",
"with": {
"asdf-system": "example"
}
}
]
}
}
}

Caching

To significantly speed up our tests, we can cache installed Roswell, Qlot and Common Lisp fasl files.

To accomplish this task, you don't need to dig into GitHub's docs anymore! Just add one line :cache t to your workflow definition:

(defworkflow docs
:on-push-to"master":cachet:jobs ((40ants-ci/jobs/docs:build-docs)))

Here is the diff of the generated workflow file. It shows steps, added automatically:

modified .github/workflows/docs.yml
@@ -20,13 +20,40 @@
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
+ {+ "name": "Grant All Perms to Make Cache Restoring Possible",+ "run": "sudo mkdir -p /usr/local/etc/roswell\n sudo chown \"${USER}\" /usr/local/etc/roswell\n # Here the ros binary will be restored:\n sudo chown \"${USER}\" /usr/local/bin",+ "shell": "bash"+ },+ {+ "name": "Get Current Month",+ "id": "current-month",+ "run": "echo \"::set-output name=value::$(date -u \"+%Y-%m\")\"",+ "shell": "bash"+ },+ {+ "name": "Cache Roswell Setup",+ "id": "cache",+ "uses": "actions/cache@v3",+ "with": {+ "path": "qlfile\n qlfile.lock\n /usr/local/bin/ros\n ~/.cache/common-lisp/\n ~/.roswell\n /usr/local/etc/roswell\n .qlot",+ "key": "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-ubuntu-latest-quicklisp-sbcl-bin-${{ hashFiles('qlfile.lock') }}"+ }+ },+ {+ "name": "Restore Path To Cached Files",+ "run": "echo $HOME/.roswell/bin >> $GITHUB_PATH\n echo .qlot/bin >> $GITHUB_PATH",+ "shell": "bash",+ "if": "steps.cache.outputs.cache-hit == 'true'"+ },
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "40ants-ci",
"qlfile-template": ""
- }+ },+ "if": "steps.cache.outputs.cache-hit != 'true'"
},
{

Adding env variables

You can specify additional environment variables on any level of the GitHub workflow: for workflow itself, for a job or for a step.

To specify env for workflow or a job, just add an ENV argument with alist or plist value like this:

(defworkflow release
:on-push-to"master":env (:github-token"${{ secrets.autotag_token }}")
:jobs ((40ants-ci/jobs/autotag:autotag)))

or as alist:

(defworkflow release
:on-push-to"master":env (("github_token"."${{ secrets.autotag_token }}"))
:jobs ((40ants-ci/jobs/autotag:autotag)))

or for the job itself:

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag
:env (:github-token"${{ secrets.autotag_token }}"))))

the same way it can be specified on a custom step:

(40ants-ci/steps/sh:sh "Custom env-var example""echo $CUSTOM_VAR":env (:custom-var"Hello world!"))

Note - environment variable names are always transformed to uppercase and dashes are replaced with underscores.

Running custom steps

Sometimes you might need to install custom system packages or do something before the job will finish. To accomplish these task you can provide custom steps using BEFORE-STEPS argument or AFTER-STEPS argument.

Here is an example where we are installing system package libunaq1-dev before running the testsuite:

(defparameter*required-steps*
(list (sh "Install libunac""sudo apt-get install -y libunac1-dev")))
(defworkflow ci
:on-pull-requestt:cachet:jobs ((run-tests
:steps-before*required-steps*:asdf-system"my-asdf-system")))

Details

TODO: I have to write a few chapters with details on additional job's parameters and a way how to create new job types.

But for now, I want to show a small example, how to define a workflow with a job which takes care about lisp installation and then calls a custom step:

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:cachet:jobs ((40ants-ci/jobs/lisp-job:lisp-job :name"check-ros-config":lisp"ccl-bin":steps ((40ants-ci/steps/sh:sh "Show Roswell Config""ros config")))))

Here we are using the class 40ants-ci/jobs/lisp-job:lisp-job which is base for most classes in this ASDF system and pass a custom 40ants-ci/steps/sh:sh (12) step to it. This step will be called after the repostory checkout and CCL-BIN lisp installation. so, thus when this step will run ros config command, it will output something like that:

asdf.version=3.3.5.3
ccl-bin.version=1.12.2
setup.time=3918000017
sbcl-bin.version=2.4.1
default.lisp=ccl-bin
Possible subcommands:
set
show

Pay attention to the NAME argument of 40ants-ci/jobs/lisp-job:lisp-job class. If you omit it, then default "lisp-job" name will be used.

API

40ANTS-CI

package40ants-ci

Functions

function40ants-ci:generate system &key path

Generates GitHub workflow for given ASDF system.

This function searches workflow definitions in all packages of the given ASDF system.

If PATH argument is not given, workflow files will be written to .github/workflow/ relarive to the SYSTEM.

40ANTS-CI/GITHUB

package40ants-ci/github

Generics

generic-function40ants-ci/github:generate obj path

generic-function40ants-ci/github:prepare-data obj

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

40ANTS-CI/JOBS/AUTOTAG

package40ants-ci/jobs/autotag

Classes

AUTOTAG

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Readers

reader40ants-ci/jobs/autotag:filename (autotag) (:filename = *default-filename*)

File where to search for version numbers.

reader40ants-ci/jobs/autotag:regex (autotag) (:regex = *default-regex*)

Regexp used to extract version numbers.

reader40ants-ci/jobs/autotag:tag-prefix (autotag) (:tag-prefix = *default-tag-prefix*)

Tag prefix.

reader40ants-ci/jobs/autotag:token-pattern (autotag) (:token-pattern = *default-token-pattern*)

Auth token pattern.

Functions

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

40ANTS-CI/JOBS/CRITIC

package40ants-ci/jobs/critic

Classes

CRITIC

class40ants-ci/jobs/critic:critic (lisp-job)

Readers

reader40ants-ci/jobs/critic:asdf-systems (critic) (:asdf-systems)

Critic can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/critic:ignore-critiques (critic) (:ignore-critiques)

A list strigns with names of critiques to ignore.

Functions

function40ants-ci/jobs/critic:critic &key asdf-systems asdf-version ignore-critiques env

Creates a job which will run Lisp Critic for given ASDF systems.

If argument ASDF-SYSTEMS is NIL, it will use ASDF system to which current lisp file is belong.

You may also provide ASDF-VERSION argument. It should be a string. By default, the latest ASDF version will be used.

40ANTS-CI/JOBS/DOCS

package40ants-ci/jobs/docs

Classes

BUILD-DOCS

class40ants-ci/jobs/docs:build-docs (lisp-job)

Builds documentation and uploads it to GitHub using "40ants/build-docs" github action.

Readers

reader40ants-ci/jobs/docs:error-on-warnings (build-docs) (:error-on-warnings = t)

Functions

function40ants-ci/jobs/docs:build-docs &rest args &key (error-on-warnings t) os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp asdf-system qlfile checkout-submodules dynamic-space-size

Creates a job of class build-docs.

40ANTS-CI/JOBS/JOB

package40ants-ci/jobs/job

Classes

JOB

class40ants-ci/jobs/job:job ()

Readers

reader40ants-ci/jobs/job:exclude (job) (:exclude = nil)

A list of plists denoting matrix combinations to be excluded.

reader40ants-ci/jobs/job:explicit-steps (job) (:steps = nil)

This slot holds steps given as a STEPS argument to a job constructor. Depending on a job class, it might add additional steps around these explicit steps.

reader40ants-ci/jobs/job:job-env (job) (:env = nil)

An alist of environment variables and their values to be added on job level. Values are evaluated in runtime.

reader40ants-ci/jobs/job:name (job) (:name)

If this name was not given in constructor, then name will be lowercased name of the job class.

reader40ants-ci/jobs/job:os (job) (:OS = "ubuntu-latest")

reader40ants-ci/jobs/job:permissions (job) (:permissions = nil)

A plist of permissions need for running the job.

These permissions will be bound to secrets.GITHUB_TOKEN variable. Use default-initargs to override permissions in subclasses:

(:default-initargs:permissions'(:content"write"))

reader40ants-ci/jobs/job:steps-after (job) (:steps-after = nil)

This slot holds steps given as a STEPS-AFTER argument to a job constructor. These steps will be appended to steps returned by the job class.

reader40ants-ci/jobs/job:steps-before (job) (:steps-before = nil)

This slot holds steps given as a STEPS-BEFORE argument to a job constructor. These steps will be prepended to steps returned by the job class.

Generics

generic-function40ants-ci/jobs/job:make-env job

generic-function40ants-ci/jobs/job:make-matrix job

generic-function40ants-ci/jobs/job:make-permissions job

Should return an alist with mapping from string to string where keys are scopes and values are permission names. Default method generates this alist from the plist of job's "permissions" slot.

generic-function40ants-ci/jobs/job:steps job

generic-function40ants-ci/jobs/job:use-matrix-p job

40ANTS-CI/JOBS/LINTER

package40ants-ci/jobs/linter

Classes

LINTER

class40ants-ci/jobs/linter:linter (lisp-job)

Readers

reader40ants-ci/jobs/linter:asdf-systems (linter) (:asdf-systems = nil)

Linter can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/linter:check-imports (linter) (:check-imports = nil)

Linter will check for missing or unused imports of package-inferred systems.

Functions

function40ants-ci/jobs/linter:linter &rest args &key asdf-systems check-imports os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp qlfile checkout-submodules dynamic-space-size

Creates a job which will run SBLint for given ASDF systems.

If no ASD files given, it will use all ASD files from the current ASDF system.

40ANTS-CI/JOBS/LISP-JOB

package40ants-ci/jobs/lisp-job

Classes

LISP-JOB

class40ants-ci/jobs/lisp-job:lisp-job (job)

This job checkouts the sources, installs Roswell and Qlot. Also, it caches results between runs.

Readers

reader40ants-ci/jobs/lisp-job:asdf-system (lisp-job) (:asdf-system = nil)

reader40ants-ci/jobs/lisp-job:asdf-version (lisp-job) (:asdf-version = nil)

ASDF version to use when setting up Lisp environment. If NIL, then the latest will be used.

reader40ants-ci/jobs/lisp-job:checkout-submodules (lisp-job) (:checkout-submodules = nil)

If this flag is true, then we will command actions/checkout action to checkout submodules.

reader40ants-ci/jobs/lisp-job:dynamic-space-size (lisp-job) (:dynamic-space-size = nil)

Dynamic space size for SBCL.

reader40ants-ci/jobs/lisp-job:lisp (lisp-job) (:LISP = "sbcl-bin")

reader40ants-ci/jobs/lisp-job:qlfile (lisp-job) (:qlfile = nil)

reader40ants-ci/jobs/lisp-job:qlot-version (lisp-job) (:qlot-version = nil)

Qlot version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

reader40ants-ci/jobs/lisp-job:quicklisp (lisp-job) (:QUICKLISP = "quicklisp")

reader40ants-ci/jobs/lisp-job:roswell-version (lisp-job) (:roswell-version = nil)

Roswell version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

40ANTS-CI/JOBS/RUN-TESTS

package40ants-ci/jobs/run-tests

Classes

RUN-TESTS

class40ants-ci/jobs/run-tests:run-tests (lisp-job)

This job test runs tests for a given ASDF system.

Readers

reader40ants-ci/jobs/run-tests:coverage (run-tests) (:coverage = nil)

reader40ants-ci/jobs/run-tests:custom (run-tests) (:custom = nil)

Functions

function40ants-ci/jobs/run-tests:run-tests &rest rest &key coverage custom os permissions steps steps-before steps-after env roswell-version asdf-version qlot-version lisp exclude qlfile quicklisp asdf-system checkout-submodules dynamic-space-size

Creates a job step of class run-tests.

40ANTS-CI/STEPS/ACTION

package40ants-ci/steps/action

Classes

ACTION

class40ants-ci/steps/action:action (step)

Readers

reader40ants-ci/steps/action:action-args (action) (:args)

A plist to be passed as "with" dictionary to the action.

reader40ants-ci/steps/action:uses (action) (:uses)

Functions

function40ants-ci/steps/action:action name uses &rest args &key id if env &allow-other-keys

40ANTS-CI/STEPS/SH

package40ants-ci/steps/sh

Classes

SH

class40ants-ci/steps/sh:sh (step)

Readers

reader40ants-ci/steps/sh:command (sh) (:command)

reader40ants-ci/steps/sh:shell (sh) (:shell = *default-shell*)

Functions

function40ants-ci/steps/sh:sh name command &key id if (shell *default-shell*) env

Macros

macro40ants-ci/steps/sh:sections &body body

Returns a string with a bash script where some parts are grouped.

In this example we have 3 sections:

(sections
("Help Argument""qlot exec cl-info --help")
("Version Argument""qlot exec cl-info --version")
("Lisp Systems Info""qlot exec cl-info""qlot exec cl-info cl-info defmain"))

It will be compiled into:

echo ::group::Help Argument
qlot exec cl-info --help
echo ::endgroup::
echo ::group::Version Argument
qlot exec cl-info --version
echo ::endgroup::
echo ::group::Lisp Systems Info
qlot exec cl-info
qlot exec cl-info cl-info defmain
echo ::endgroup::

40ANTS-CI/STEPS/STEP

package40ants-ci/steps/step

Classes

STEP

class40ants-ci/steps/step:step ()

Readers

reader40ants-ci/steps/step:env (step) (:env = nil)

An alist of environment variables.

reader40ants-ci/steps/step:step-id (step) (:id = nil)

reader40ants-ci/steps/step:step-if (step) (:if = nil)

reader40ants-ci/steps/step:step-name (step) (:name = nil)

40ANTS-CI/UTILS

package40ants-ci/utils

Generics

generic-function40ants-ci/utils:system-packages system

Returns a list of packages created by ASDF system.

Default implementation returns a package having the same name as a system and all packages matched to package-inferred subsystems:

CL-USER> (docs-builder/utils:system-packages :docs-builder)
(#<PACKAGE "DOCS-BUILDER">
#<PACKAGE "DOCS-BUILDER/UTILS">
#<PACKAGE "DOCS-BUILDER/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/GENEVA/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/GUESSER">
#<PACKAGE "DOCS-BUILDER/DOCS">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/BUILDER">)

Functions

function40ants-ci/utils:alistp list

Test wheather LIST argument is a properly formed alist.

In this library, alist has always a string as a key. Because we need them to have this form to serialize to JSON propertly.

(alistp '(("cron" . "0 10 * * 1"))) -> T (alistp '((("cron" . "0 10 * * 1")))) -> NIL

function40ants-ci/utils:current-system-name

function40ants-ci/utils:dedent text

Removes common leading whitespace from each string.

A few examples:

(dedent "Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "
Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD.")
"This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD."

function40ants-ci/utils:ensure-list-of-plists data

function40ants-ci/utils:ensure-primary-system system

function40ants-ci/utils:make-github-workflows-path system

function40ants-ci/utils:plist-to-alist plist &key (string-keys t) (lowercase t)

Make an alist from a plist PLIST.

By default, transforms keys to lowercased strings

function40ants-ci/utils:plistp list

Test wheather LIST is a properly formed plist.

function40ants-ci/utils:single list

Test wheather LIST contains exactly 1 element.

function40ants-ci/utils:to-json data

40ANTS-CI/VARS

package40ants-ci/vars

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

variable40ants-ci/vars:*use-cache* nil

Workflow will set this variable when preparing the data or YAML generation.

40ANTS-CI/WORKFLOW

package40ants-ci/workflow

Classes

WORKFLOW

class40ants-ci/workflow:workflow ()

Readers

reader40ants-ci/workflow:by-cron (workflow) (:by-cron = "0 10 * * 1")

reader40ants-ci/workflow:cache-p (workflow) (:cache = t)

reader40ants-ci/workflow:jobs (workflow) (:jobs = nil)

reader40ants-ci/workflow:name (workflow) (:name)

reader40ants-ci/workflow:on-pull-request (workflow) (:on-pull-request = t)

reader40ants-ci/workflow:on-push-to (workflow) (:ON-PUSH-TO = "master")

reader40ants-ci/workflow:workflow-env (workflow) (:env = nil)

An alist of environment variables and their values to be added on workflow level. Values are evaluated in runtime.

Macros

macro40ants-ci/workflow:defworkflow name &key on-push-to by-cron on-pull-request cache env jobs

[generated by 40ANTS-DOC]

About

Highly opionated Github Actions workflow builder for Common Lisp projects.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

40Ants-CI - Github Workflow Generator

This is a small utility, which can generate GitHub workflows for Common Lisp projects.

It generates workflow for running tests and building docs. These workflows use 40ants/run-tests and 40ants/build-docs actions and SBLint to check code for compilation errors.

40ANTS-CI ASDF System Details

Reasons to Use

  • This system hides all entrails related to caching.
  • Includes a few ready to use job types.
  • Custom job types can be defined and distributed as separate ASDF systems.
  • You don't have to write YAML anymore!

Quickstart

This system allows you to define workflows in the lisp code. The best way is to make these definitions a part of your ASDF system. This way 40ants-ci (12) will be able to automatically understand for which system it builds a workflow.

Each workflow consists of jobs and each job is a number of steps.

There are three predefine types of jobs and you can create your own. Predefined jobs allows to reuse steps in multiple CL libraries.

In next examples, I'll presume you are writing code in a file which is the part of the package inferred ASDF system EXAMPLE/CI. A file should have the following header:

(defpackage#:example/ci
(:use#:cl)
(:import-from#:40ants-ci/workflow
#:defworkflow)
(:import-from#:40ants-ci/jobs/linter)
(:import-from#:40ants-ci/jobs/run-tests)
(:import-from#:40ants-ci/jobs/docs))

Job Types

Autotag

This job is automates git tag placement on the commit where you have changed the ChangeLog.md.

This can be a useful to automate package deployment and releases. You update the changelog, a job pushes a new git tag and the next action triggers on this tag and build a release.

Or you if you publish your library at Quicklisp distribution, then you can change it's source type to the latest-github-tag to provide more stable releases to your users. This way you commits into master will be ignored until you change the changelog and git tag will be pushed. Here is an example how to setup this kind of quicklisp project source.

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag)))

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Linter

The simplest job type is linter. It loads a

(defworkflow linter
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)))

When you'll hit C-c C-c on this definition, it will generate .github/workflows/linter.yml with following content:

{
"name": "LINTER",
"on": {
"pull_request": null
},
"jobs": {
"linter": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Install SBLint",
"run": "qlot exec ros install cxxxr/sblint",
"shell": "bash"
},
{
"name": "Run Linter",
"run": "qlot exec sblint example.asd",
"shell": "bash"
}
]
}
}
}

Here you can see, a few steps in the job:

  1. Checkout the code.
  2. Install Roswell & Qlot using 40ants/setup-lisp action.
  3. Install SBLint.
  4. Run linter for example.asd.

Another interesting thing is that this workflow automatically uses ubuntu-latestOS, Quicklisp and sbcl-bin Lisp implementation. Later I'll show you how to redefine these settings.

class40ants-ci/jobs/linter:linter (lisp-job)

Critic

This job is similar to linter, but instead of SBLint it runs Lisp Critic.

Lisp Critic is a program which advices how to make you Common Lisp code more idiomatic, readable and performant. Also, sometimes it might catch logical errors in the code.

Here is how you can add this job type in your workflow:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/critic:critic)))

Also, you might combine this job together with others, for example, with linter:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)
(40ants-ci/jobs/critic:critic)))

and they will be executed in parallel. See docs on 40ants-ci/jobs/critic:critic function to learn about supported arguments.

Running Tests

Another interesting job type is 40ants-ci/jobs/run-tests:run-tests (12).

When using this job type, make sure, your system runs tests on (ASDF:TEST-SYSTEM :system-name) call and signals error if something went wrong.

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:jobs ((40ants-ci/jobs/run-tests:run-tests
:coveraget)))

Here I've added a few options to the workflow:

  • by-cron - sets a schedule.
  • on-push-to - defines a branch or branches to track.

It will generate .github/workflows/ci.yml with following content:

{
"name": "CI",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": null,
"schedule": [
{
"cron": "0 10 * * 1"
}
]
},
"jobs": {
"run-tests": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Run Tests",
"uses": "40ants/run-tests@v2",
"with": {
"asdf-system": "example",
"coveralls-token": "${{ secrets.github_token }}"
}
}
]
}
}
}

The result is similar to the workflow generated for Linter, but uses 40ants/setup-lisp action at the final step.

Also, I've passed an option :coverage t to the job. Thus coverage report will be uploaded to Coveralls.io automatically.

Defining a test Matrix

Lisp has many implementations and can be used on multiple platforms. Thus it is a good idea to test our software on many combinations of OS and lisp implementations. Workflow generator makes this very easy.

Here is an example of workflow definition with three dimentional matrix. It not only tests a library under different lisps and OS, but also checks if it works with the latest Quicklisp and Ultralisp distributions:

(defworkflow ci
:on-pull-requestt:jobs ((run-tests
:os ("ubuntu-latest""macos-latest")
:quicklisp ("quicklisp""ultralisp")
:lisp ("sbcl-bin""ccl-bin""allegro""clisp""cmucl")
:exclude (;; Seems allegro is does not support 64bit OSX.;; Unable to install it using Roswell:;; alisp is not executable. Missing 32bit glibc?
(:os"macos-latest":lisp"allegro")))))

Multiple jobs

Besides a build matrix, you might specify a multiple jobs of the same type, but with different parameters:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:lisp"sbcl-bin")
(run-tests
:lisp"ccl-bin")
(run-tests
:lisp"allegro")))

This will generate a workflow with three jobs: "run-tests", "run-tests-2" and "run-tests-3".

Meaningful names might be specified as well:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:name"test-on-sbcl":lisp"sbcl-bin")
(run-tests
:name"test-on-ccl":lisp"ccl-bin")
(run-tests
:name"test-on-allegro":lisp"allegro")))

Here is how these jobs will look like in the GitHub interface:

Building Docs

Third predefined job type is 40ants-ci/jobs/docs:build-docs (12). It uses 40ants/build-docs action and will work only if your ASDF system uses a documentation builder supported by 40ants/docs-builder.

To build docs on every push to master, just use this code:

(defworkflow docs
:on-push-to"master":jobs ((40ants-ci/jobs/docs:build-docs)))

It will generate .github/workflows/docs.yml with following content:

{
"name": "DOCS",
"on": {
"push": {
"branches": [
"master"
]
}
},
"jobs": {
"build-docs": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example",
"qlfile-template": ""
}
},
{
"name": "Build Docs",
"uses": "40ants/build-docs@v1",
"with": {
"asdf-system": "example"
}
}
]
}
}
}

Caching

To significantly speed up our tests, we can cache installed Roswell, Qlot and Common Lisp fasl files.

To accomplish this task, you don't need to dig into GitHub's docs anymore! Just add one line :cache t to your workflow definition:

(defworkflow docs
:on-push-to"master":cachet:jobs ((40ants-ci/jobs/docs:build-docs)))

Here is the diff of the generated workflow file. It shows steps, added automatically:

modified .github/workflows/docs.yml
@@ -20,13 +20,40 @@
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
+ {+ "name": "Grant All Perms to Make Cache Restoring Possible",+ "run": "sudo mkdir -p /usr/local/etc/roswell\n sudo chown \"${USER}\" /usr/local/etc/roswell\n # Here the ros binary will be restored:\n sudo chown \"${USER}\" /usr/local/bin",+ "shell": "bash"+ },+ {+ "name": "Get Current Month",+ "id": "current-month",+ "run": "echo \"::set-output name=value::$(date -u \"+%Y-%m\")\"",+ "shell": "bash"+ },+ {+ "name": "Cache Roswell Setup",+ "id": "cache",+ "uses": "actions/cache@v3",+ "with": {+ "path": "qlfile\n qlfile.lock\n /usr/local/bin/ros\n ~/.cache/common-lisp/\n ~/.roswell\n /usr/local/etc/roswell\n .qlot",+ "key": "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-ubuntu-latest-quicklisp-sbcl-bin-${{ hashFiles('qlfile.lock') }}"+ }+ },+ {+ "name": "Restore Path To Cached Files",+ "run": "echo $HOME/.roswell/bin >> $GITHUB_PATH\n echo .qlot/bin >> $GITHUB_PATH",+ "shell": "bash",+ "if": "steps.cache.outputs.cache-hit == 'true'"+ },
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "40ants-ci",
"qlfile-template": ""
- }+ },+ "if": "steps.cache.outputs.cache-hit != 'true'"
},
{

Adding env variables

You can specify additional environment variables on any level of the GitHub workflow: for workflow itself, for a job or for a step.

To specify env for workflow or a job, just add an ENV argument with alist or plist value like this:

(defworkflow release
:on-push-to"master":env (:github-token"${{ secrets.autotag_token }}")
:jobs ((40ants-ci/jobs/autotag:autotag)))

or as alist:

(defworkflow release
:on-push-to"master":env (("github_token"."${{ secrets.autotag_token }}"))
:jobs ((40ants-ci/jobs/autotag:autotag)))

or for the job itself:

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag
:env (:github-token"${{ secrets.autotag_token }}"))))

the same way it can be specified on a custom step:

(40ants-ci/steps/sh:sh "Custom env-var example""echo $CUSTOM_VAR":env (:custom-var"Hello world!"))

Note - environment variable names are always transformed to uppercase and dashes are replaced with underscores.

Running custom steps

Sometimes you might need to install custom system packages or do something before the job will finish. To accomplish these task you can provide custom steps using BEFORE-STEPS argument or AFTER-STEPS argument.

Here is an example where we are installing system package libunaq1-dev before running the testsuite:

(defparameter*required-steps*
(list (sh "Install libunac""sudo apt-get install -y libunac1-dev")))
(defworkflow ci
:on-pull-requestt:cachet:jobs ((run-tests
:steps-before*required-steps*:asdf-system"my-asdf-system")))

Details

TODO: I have to write a few chapters with details on additional job's parameters and a way how to create new job types.

But for now, I want to show a small example, how to define a workflow with a job which takes care about lisp installation and then calls a custom step:

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:cachet:jobs ((40ants-ci/jobs/lisp-job:lisp-job :name"check-ros-config":lisp"ccl-bin":steps ((40ants-ci/steps/sh:sh "Show Roswell Config""ros config")))))

Here we are using the class 40ants-ci/jobs/lisp-job:lisp-job which is base for most classes in this ASDF system and pass a custom 40ants-ci/steps/sh:sh (12) step to it. This step will be called after the repostory checkout and CCL-BIN lisp installation. so, thus when this step will run ros config command, it will output something like that:

asdf.version=3.3.5.3
ccl-bin.version=1.12.2
setup.time=3918000017
sbcl-bin.version=2.4.1
default.lisp=ccl-bin
Possible subcommands:
set
show

Pay attention to the NAME argument of 40ants-ci/jobs/lisp-job:lisp-job class. If you omit it, then default "lisp-job" name will be used.

API

40ANTS-CI

package40ants-ci

Functions

function40ants-ci:generate system &key path

Generates GitHub workflow for given ASDF system.

This function searches workflow definitions in all packages of the given ASDF system.

If PATH argument is not given, workflow files will be written to .github/workflow/ relarive to the SYSTEM.

40ANTS-CI/GITHUB

package40ants-ci/github

Generics

generic-function40ants-ci/github:generate obj path

generic-function40ants-ci/github:prepare-data obj

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

40ANTS-CI/JOBS/AUTOTAG

package40ants-ci/jobs/autotag

Classes

AUTOTAG

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Readers

reader40ants-ci/jobs/autotag:filename (autotag) (:filename = *default-filename*)

File where to search for version numbers.

reader40ants-ci/jobs/autotag:regex (autotag) (:regex = *default-regex*)

Regexp used to extract version numbers.

reader40ants-ci/jobs/autotag:tag-prefix (autotag) (:tag-prefix = *default-tag-prefix*)

Tag prefix.

reader40ants-ci/jobs/autotag:token-pattern (autotag) (:token-pattern = *default-token-pattern*)

Auth token pattern.

Functions

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

40ANTS-CI/JOBS/CRITIC

package40ants-ci/jobs/critic

Classes

CRITIC

class40ants-ci/jobs/critic:critic (lisp-job)

Readers

reader40ants-ci/jobs/critic:asdf-systems (critic) (:asdf-systems)

Critic can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/critic:ignore-critiques (critic) (:ignore-critiques)

A list strigns with names of critiques to ignore.

Functions

function40ants-ci/jobs/critic:critic &key asdf-systems asdf-version ignore-critiques env

Creates a job which will run Lisp Critic for given ASDF systems.

If argument ASDF-SYSTEMS is NIL, it will use ASDF system to which current lisp file is belong.

You may also provide ASDF-VERSION argument. It should be a string. By default, the latest ASDF version will be used.

40ANTS-CI/JOBS/DOCS

package40ants-ci/jobs/docs

Classes

BUILD-DOCS

class40ants-ci/jobs/docs:build-docs (lisp-job)

Builds documentation and uploads it to GitHub using "40ants/build-docs" github action.

Readers

reader40ants-ci/jobs/docs:error-on-warnings (build-docs) (:error-on-warnings = t)

Functions

function40ants-ci/jobs/docs:build-docs &rest args &key (error-on-warnings t) os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp asdf-system qlfile checkout-submodules dynamic-space-size

Creates a job of class build-docs.

40ANTS-CI/JOBS/JOB

package40ants-ci/jobs/job

Classes

JOB

class40ants-ci/jobs/job:job ()

Readers

reader40ants-ci/jobs/job:exclude (job) (:exclude = nil)

A list of plists denoting matrix combinations to be excluded.

reader40ants-ci/jobs/job:explicit-steps (job) (:steps = nil)

This slot holds steps given as a STEPS argument to a job constructor. Depending on a job class, it might add additional steps around these explicit steps.

reader40ants-ci/jobs/job:job-env (job) (:env = nil)

An alist of environment variables and their values to be added on job level. Values are evaluated in runtime.

reader40ants-ci/jobs/job:name (job) (:name)

If this name was not given in constructor, then name will be lowercased name of the job class.

reader40ants-ci/jobs/job:os (job) (:OS = "ubuntu-latest")

reader40ants-ci/jobs/job:permissions (job) (:permissions = nil)

A plist of permissions need for running the job.

These permissions will be bound to secrets.GITHUB_TOKEN variable. Use default-initargs to override permissions in subclasses:

(:default-initargs:permissions'(:content"write"))

reader40ants-ci/jobs/job:steps-after (job) (:steps-after = nil)

This slot holds steps given as a STEPS-AFTER argument to a job constructor. These steps will be appended to steps returned by the job class.

reader40ants-ci/jobs/job:steps-before (job) (:steps-before = nil)

This slot holds steps given as a STEPS-BEFORE argument to a job constructor. These steps will be prepended to steps returned by the job class.

Generics

generic-function40ants-ci/jobs/job:make-env job

generic-function40ants-ci/jobs/job:make-matrix job

generic-function40ants-ci/jobs/job:make-permissions job

Should return an alist with mapping from string to string where keys are scopes and values are permission names. Default method generates this alist from the plist of job's "permissions" slot.

generic-function40ants-ci/jobs/job:steps job

generic-function40ants-ci/jobs/job:use-matrix-p job

40ANTS-CI/JOBS/LINTER

package40ants-ci/jobs/linter

Classes

LINTER

class40ants-ci/jobs/linter:linter (lisp-job)

Readers

reader40ants-ci/jobs/linter:asdf-systems (linter) (:asdf-systems = nil)

Linter can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/linter:check-imports (linter) (:check-imports = nil)

Linter will check for missing or unused imports of package-inferred systems.

Functions

function40ants-ci/jobs/linter:linter &rest args &key asdf-systems check-imports os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp qlfile checkout-submodules dynamic-space-size

Creates a job which will run SBLint for given ASDF systems.

If no ASD files given, it will use all ASD files from the current ASDF system.

40ANTS-CI/JOBS/LISP-JOB

package40ants-ci/jobs/lisp-job

Classes

LISP-JOB

class40ants-ci/jobs/lisp-job:lisp-job (job)

This job checkouts the sources, installs Roswell and Qlot. Also, it caches results between runs.

Readers

reader40ants-ci/jobs/lisp-job:asdf-system (lisp-job) (:asdf-system = nil)

reader40ants-ci/jobs/lisp-job:asdf-version (lisp-job) (:asdf-version = nil)

ASDF version to use when setting up Lisp environment. If NIL, then the latest will be used.

reader40ants-ci/jobs/lisp-job:checkout-submodules (lisp-job) (:checkout-submodules = nil)

If this flag is true, then we will command actions/checkout action to checkout submodules.

reader40ants-ci/jobs/lisp-job:dynamic-space-size (lisp-job) (:dynamic-space-size = nil)

Dynamic space size for SBCL.

reader40ants-ci/jobs/lisp-job:lisp (lisp-job) (:LISP = "sbcl-bin")

reader40ants-ci/jobs/lisp-job:qlfile (lisp-job) (:qlfile = nil)

reader40ants-ci/jobs/lisp-job:qlot-version (lisp-job) (:qlot-version = nil)

Qlot version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

reader40ants-ci/jobs/lisp-job:quicklisp (lisp-job) (:QUICKLISP = "quicklisp")

reader40ants-ci/jobs/lisp-job:roswell-version (lisp-job) (:roswell-version = nil)

Roswell version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

40ANTS-CI/JOBS/RUN-TESTS

package40ants-ci/jobs/run-tests

Classes

RUN-TESTS

class40ants-ci/jobs/run-tests:run-tests (lisp-job)

This job test runs tests for a given ASDF system.

Readers

reader40ants-ci/jobs/run-tests:coverage (run-tests) (:coverage = nil)

reader40ants-ci/jobs/run-tests:custom (run-tests) (:custom = nil)

Functions

function40ants-ci/jobs/run-tests:run-tests &rest rest &key coverage custom os permissions steps steps-before steps-after env roswell-version asdf-version qlot-version lisp exclude qlfile quicklisp asdf-system checkout-submodules dynamic-space-size

Creates a job step of class run-tests.

40ANTS-CI/STEPS/ACTION

package40ants-ci/steps/action

Classes

ACTION

class40ants-ci/steps/action:action (step)

Readers

reader40ants-ci/steps/action:action-args (action) (:args)

A plist to be passed as "with" dictionary to the action.

reader40ants-ci/steps/action:uses (action) (:uses)

Functions

function40ants-ci/steps/action:action name uses &rest args &key id if env &allow-other-keys

40ANTS-CI/STEPS/SH

package40ants-ci/steps/sh

Classes

SH

class40ants-ci/steps/sh:sh (step)

Readers

reader40ants-ci/steps/sh:command (sh) (:command)

reader40ants-ci/steps/sh:shell (sh) (:shell = *default-shell*)

Functions

function40ants-ci/steps/sh:sh name command &key id if (shell *default-shell*) env

Macros

macro40ants-ci/steps/sh:sections &body body

Returns a string with a bash script where some parts are grouped.

In this example we have 3 sections:

(sections
("Help Argument""qlot exec cl-info --help")
("Version Argument""qlot exec cl-info --version")
("Lisp Systems Info""qlot exec cl-info""qlot exec cl-info cl-info defmain"))

It will be compiled into:

echo ::group::Help Argument
qlot exec cl-info --help
echo ::endgroup::
echo ::group::Version Argument
qlot exec cl-info --version
echo ::endgroup::
echo ::group::Lisp Systems Info
qlot exec cl-info
qlot exec cl-info cl-info defmain
echo ::endgroup::

40ANTS-CI/STEPS/STEP

package40ants-ci/steps/step

Classes

STEP

class40ants-ci/steps/step:step ()

Readers

reader40ants-ci/steps/step:env (step) (:env = nil)

An alist of environment variables.

reader40ants-ci/steps/step:step-id (step) (:id = nil)

reader40ants-ci/steps/step:step-if (step) (:if = nil)

reader40ants-ci/steps/step:step-name (step) (:name = nil)

40ANTS-CI/UTILS

package40ants-ci/utils

Generics

generic-function40ants-ci/utils:system-packages system

Returns a list of packages created by ASDF system.

Default implementation returns a package having the same name as a system and all packages matched to package-inferred subsystems:

CL-USER> (docs-builder/utils:system-packages :docs-builder)
(#<PACKAGE "DOCS-BUILDER">
#<PACKAGE "DOCS-BUILDER/UTILS">
#<PACKAGE "DOCS-BUILDER/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/GENEVA/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/GUESSER">
#<PACKAGE "DOCS-BUILDER/DOCS">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/BUILDER">)

Functions

function40ants-ci/utils:alistp list

Test wheather LIST argument is a properly formed alist.

In this library, alist has always a string as a key. Because we need them to have this form to serialize to JSON propertly.

(alistp '(("cron" . "0 10 * * 1"))) -> T (alistp '((("cron" . "0 10 * * 1")))) -> NIL

function40ants-ci/utils:current-system-name

function40ants-ci/utils:dedent text

Removes common leading whitespace from each string.

A few examples:

(dedent "Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "
Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD.")
"This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD."

function40ants-ci/utils:ensure-list-of-plists data

function40ants-ci/utils:ensure-primary-system system

function40ants-ci/utils:make-github-workflows-path system

function40ants-ci/utils:plist-to-alist plist &key (string-keys t) (lowercase t)

Make an alist from a plist PLIST.

By default, transforms keys to lowercased strings

function40ants-ci/utils:plistp list

Test wheather LIST is a properly formed plist.

function40ants-ci/utils:single list

Test wheather LIST contains exactly 1 element.

function40ants-ci/utils:to-json data

40ANTS-CI/VARS

package40ants-ci/vars

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

variable40ants-ci/vars:*use-cache* nil

Workflow will set this variable when preparing the data or YAML generation.

40ANTS-CI/WORKFLOW

package40ants-ci/workflow

Classes

WORKFLOW

class40ants-ci/workflow:workflow ()

Readers

reader40ants-ci/workflow:by-cron (workflow) (:by-cron = "0 10 * * 1")

reader40ants-ci/workflow:cache-p (workflow) (:cache = t)

reader40ants-ci/workflow:jobs (workflow) (:jobs = nil)

reader40ants-ci/workflow:name (workflow) (:name)

reader40ants-ci/workflow:on-pull-request (workflow) (:on-pull-request = t)

reader40ants-ci/workflow:on-push-to (workflow) (:ON-PUSH-TO = "master")

reader40ants-ci/workflow:workflow-env (workflow) (:env = nil)

An alist of environment variables and their values to be added on workflow level. Values are evaluated in runtime.

Macros

macro40ants-ci/workflow:defworkflow name &key on-push-to by-cron on-pull-request cache env jobs

[generated by 40ANTS-DOC]

About

Highly opionated Github Actions workflow builder for Common Lisp projects.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

40Ants-CI - Github Workflow Generator

This is a small utility, which can generate GitHub workflows for Common Lisp projects.

It generates workflow for running tests and building docs. These workflows use 40ants/run-tests and 40ants/build-docs actions and SBLint to check code for compilation errors.

40ANTS-CI ASDF System Details

Reasons to Use

  • This system hides all entrails related to caching.
  • Includes a few ready to use job types.
  • Custom job types can be defined and distributed as separate ASDF systems.
  • You don't have to write YAML anymore!

Quickstart

This system allows you to define workflows in the lisp code. The best way is to make these definitions a part of your ASDF system. This way 40ants-ci (12) will be able to automatically understand for which system it builds a workflow.

Each workflow consists of jobs and each job is a number of steps.

There are three predefine types of jobs and you can create your own. Predefined jobs allows to reuse steps in multiple CL libraries.

In next examples, I'll presume you are writing code in a file which is the part of the package inferred ASDF system EXAMPLE/CI. A file should have the following header:

(defpackage#:example/ci
(:use#:cl)
(:import-from#:40ants-ci/workflow
#:defworkflow)
(:import-from#:40ants-ci/jobs/linter)
(:import-from#:40ants-ci/jobs/run-tests)
(:import-from#:40ants-ci/jobs/docs))

Job Types

Autotag

This job is automates git tag placement on the commit where you have changed the ChangeLog.md.

This can be a useful to automate package deployment and releases. You update the changelog, a job pushes a new git tag and the next action triggers on this tag and build a release.

Or you if you publish your library at Quicklisp distribution, then you can change it's source type to the latest-github-tag to provide more stable releases to your users. This way you commits into master will be ignored until you change the changelog and git tag will be pushed. Here is an example how to setup this kind of quicklisp project source.

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag)))

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Linter

The simplest job type is linter. It loads a

(defworkflow linter
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)))

When you'll hit C-c C-c on this definition, it will generate .github/workflows/linter.yml with following content:

{
"name": "LINTER",
"on": {
"pull_request": null
},
"jobs": {
"linter": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Install SBLint",
"run": "qlot exec ros install cxxxr/sblint",
"shell": "bash"
},
{
"name": "Run Linter",
"run": "qlot exec sblint example.asd",
"shell": "bash"
}
]
}
}
}

Here you can see, a few steps in the job:

  1. Checkout the code.
  2. Install Roswell & Qlot using 40ants/setup-lisp action.
  3. Install SBLint.
  4. Run linter for example.asd.

Another interesting thing is that this workflow automatically uses ubuntu-latestOS, Quicklisp and sbcl-bin Lisp implementation. Later I'll show you how to redefine these settings.

class40ants-ci/jobs/linter:linter (lisp-job)

Critic

This job is similar to linter, but instead of SBLint it runs Lisp Critic.

Lisp Critic is a program which advices how to make you Common Lisp code more idiomatic, readable and performant. Also, sometimes it might catch logical errors in the code.

Here is how you can add this job type in your workflow:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/critic:critic)))

Also, you might combine this job together with others, for example, with linter:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)
(40ants-ci/jobs/critic:critic)))

and they will be executed in parallel. See docs on 40ants-ci/jobs/critic:critic function to learn about supported arguments.

Running Tests

Another interesting job type is 40ants-ci/jobs/run-tests:run-tests (12).

When using this job type, make sure, your system runs tests on (ASDF:TEST-SYSTEM :system-name) call and signals error if something went wrong.

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:jobs ((40ants-ci/jobs/run-tests:run-tests
:coveraget)))

Here I've added a few options to the workflow:

  • by-cron - sets a schedule.
  • on-push-to - defines a branch or branches to track.

It will generate .github/workflows/ci.yml with following content:

{
"name": "CI",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": null,
"schedule": [
{
"cron": "0 10 * * 1"
}
]
},
"jobs": {
"run-tests": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Run Tests",
"uses": "40ants/run-tests@v2",
"with": {
"asdf-system": "example",
"coveralls-token": "${{ secrets.github_token }}"
}
}
]
}
}
}

The result is similar to the workflow generated for Linter, but uses 40ants/setup-lisp action at the final step.

Also, I've passed an option :coverage t to the job. Thus coverage report will be uploaded to Coveralls.io automatically.

Defining a test Matrix

Lisp has many implementations and can be used on multiple platforms. Thus it is a good idea to test our software on many combinations of OS and lisp implementations. Workflow generator makes this very easy.

Here is an example of workflow definition with three dimentional matrix. It not only tests a library under different lisps and OS, but also checks if it works with the latest Quicklisp and Ultralisp distributions:

(defworkflow ci
:on-pull-requestt:jobs ((run-tests
:os ("ubuntu-latest""macos-latest")
:quicklisp ("quicklisp""ultralisp")
:lisp ("sbcl-bin""ccl-bin""allegro""clisp""cmucl")
:exclude (;; Seems allegro is does not support 64bit OSX.;; Unable to install it using Roswell:;; alisp is not executable. Missing 32bit glibc?
(:os"macos-latest":lisp"allegro")))))

Multiple jobs

Besides a build matrix, you might specify a multiple jobs of the same type, but with different parameters:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:lisp"sbcl-bin")
(run-tests
:lisp"ccl-bin")
(run-tests
:lisp"allegro")))

This will generate a workflow with three jobs: "run-tests", "run-tests-2" and "run-tests-3".

Meaningful names might be specified as well:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:name"test-on-sbcl":lisp"sbcl-bin")
(run-tests
:name"test-on-ccl":lisp"ccl-bin")
(run-tests
:name"test-on-allegro":lisp"allegro")))

Here is how these jobs will look like in the GitHub interface:

Building Docs

Third predefined job type is 40ants-ci/jobs/docs:build-docs (12). It uses 40ants/build-docs action and will work only if your ASDF system uses a documentation builder supported by 40ants/docs-builder.

To build docs on every push to master, just use this code:

(defworkflow docs
:on-push-to"master":jobs ((40ants-ci/jobs/docs:build-docs)))

It will generate .github/workflows/docs.yml with following content:

{
"name": "DOCS",
"on": {
"push": {
"branches": [
"master"
]
}
},
"jobs": {
"build-docs": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example",
"qlfile-template": ""
}
},
{
"name": "Build Docs",
"uses": "40ants/build-docs@v1",
"with": {
"asdf-system": "example"
}
}
]
}
}
}

Caching

To significantly speed up our tests, we can cache installed Roswell, Qlot and Common Lisp fasl files.

To accomplish this task, you don't need to dig into GitHub's docs anymore! Just add one line :cache t to your workflow definition:

(defworkflow docs
:on-push-to"master":cachet:jobs ((40ants-ci/jobs/docs:build-docs)))

Here is the diff of the generated workflow file. It shows steps, added automatically:

modified .github/workflows/docs.yml
@@ -20,13 +20,40 @@
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
+ {+ "name": "Grant All Perms to Make Cache Restoring Possible",+ "run": "sudo mkdir -p /usr/local/etc/roswell\n sudo chown \"${USER}\" /usr/local/etc/roswell\n # Here the ros binary will be restored:\n sudo chown \"${USER}\" /usr/local/bin",+ "shell": "bash"+ },+ {+ "name": "Get Current Month",+ "id": "current-month",+ "run": "echo \"::set-output name=value::$(date -u \"+%Y-%m\")\"",+ "shell": "bash"+ },+ {+ "name": "Cache Roswell Setup",+ "id": "cache",+ "uses": "actions/cache@v3",+ "with": {+ "path": "qlfile\n qlfile.lock\n /usr/local/bin/ros\n ~/.cache/common-lisp/\n ~/.roswell\n /usr/local/etc/roswell\n .qlot",+ "key": "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-ubuntu-latest-quicklisp-sbcl-bin-${{ hashFiles('qlfile.lock') }}"+ }+ },+ {+ "name": "Restore Path To Cached Files",+ "run": "echo $HOME/.roswell/bin >> $GITHUB_PATH\n echo .qlot/bin >> $GITHUB_PATH",+ "shell": "bash",+ "if": "steps.cache.outputs.cache-hit == 'true'"+ },
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "40ants-ci",
"qlfile-template": ""
- }+ },+ "if": "steps.cache.outputs.cache-hit != 'true'"
},
{

Adding env variables

You can specify additional environment variables on any level of the GitHub workflow: for workflow itself, for a job or for a step.

To specify env for workflow or a job, just add an ENV argument with alist or plist value like this:

(defworkflow release
:on-push-to"master":env (:github-token"${{ secrets.autotag_token }}")
:jobs ((40ants-ci/jobs/autotag:autotag)))

or as alist:

(defworkflow release
:on-push-to"master":env (("github_token"."${{ secrets.autotag_token }}"))
:jobs ((40ants-ci/jobs/autotag:autotag)))

or for the job itself:

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag
:env (:github-token"${{ secrets.autotag_token }}"))))

the same way it can be specified on a custom step:

(40ants-ci/steps/sh:sh "Custom env-var example""echo $CUSTOM_VAR":env (:custom-var"Hello world!"))

Note - environment variable names are always transformed to uppercase and dashes are replaced with underscores.

Running custom steps

Sometimes you might need to install custom system packages or do something before the job will finish. To accomplish these task you can provide custom steps using BEFORE-STEPS argument or AFTER-STEPS argument.

Here is an example where we are installing system package libunaq1-dev before running the testsuite:

(defparameter*required-steps*
(list (sh "Install libunac""sudo apt-get install -y libunac1-dev")))
(defworkflow ci
:on-pull-requestt:cachet:jobs ((run-tests
:steps-before*required-steps*:asdf-system"my-asdf-system")))

Details

TODO: I have to write a few chapters with details on additional job's parameters and a way how to create new job types.

But for now, I want to show a small example, how to define a workflow with a job which takes care about lisp installation and then calls a custom step:

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:cachet:jobs ((40ants-ci/jobs/lisp-job:lisp-job :name"check-ros-config":lisp"ccl-bin":steps ((40ants-ci/steps/sh:sh "Show Roswell Config""ros config")))))

Here we are using the class 40ants-ci/jobs/lisp-job:lisp-job which is base for most classes in this ASDF system and pass a custom 40ants-ci/steps/sh:sh (12) step to it. This step will be called after the repostory checkout and CCL-BIN lisp installation. so, thus when this step will run ros config command, it will output something like that:

asdf.version=3.3.5.3
ccl-bin.version=1.12.2
setup.time=3918000017
sbcl-bin.version=2.4.1
default.lisp=ccl-bin
Possible subcommands:
set
show

Pay attention to the NAME argument of 40ants-ci/jobs/lisp-job:lisp-job class. If you omit it, then default "lisp-job" name will be used.

API

40ANTS-CI

package40ants-ci

Functions

function40ants-ci:generate system &key path

Generates GitHub workflow for given ASDF system.

This function searches workflow definitions in all packages of the given ASDF system.

If PATH argument is not given, workflow files will be written to .github/workflow/ relarive to the SYSTEM.

40ANTS-CI/GITHUB

package40ants-ci/github

Generics

generic-function40ants-ci/github:generate obj path

generic-function40ants-ci/github:prepare-data obj

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

40ANTS-CI/JOBS/AUTOTAG

package40ants-ci/jobs/autotag

Classes

AUTOTAG

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Readers

reader40ants-ci/jobs/autotag:filename (autotag) (:filename = *default-filename*)

File where to search for version numbers.

reader40ants-ci/jobs/autotag:regex (autotag) (:regex = *default-regex*)

Regexp used to extract version numbers.

reader40ants-ci/jobs/autotag:tag-prefix (autotag) (:tag-prefix = *default-tag-prefix*)

Tag prefix.

reader40ants-ci/jobs/autotag:token-pattern (autotag) (:token-pattern = *default-token-pattern*)

Auth token pattern.

Functions

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

40ANTS-CI/JOBS/CRITIC

package40ants-ci/jobs/critic

Classes

CRITIC

class40ants-ci/jobs/critic:critic (lisp-job)

Readers

reader40ants-ci/jobs/critic:asdf-systems (critic) (:asdf-systems)

Critic can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/critic:ignore-critiques (critic) (:ignore-critiques)

A list strigns with names of critiques to ignore.

Functions

function40ants-ci/jobs/critic:critic &key asdf-systems asdf-version ignore-critiques env

Creates a job which will run Lisp Critic for given ASDF systems.

If argument ASDF-SYSTEMS is NIL, it will use ASDF system to which current lisp file is belong.

You may also provide ASDF-VERSION argument. It should be a string. By default, the latest ASDF version will be used.

40ANTS-CI/JOBS/DOCS

package40ants-ci/jobs/docs

Classes

BUILD-DOCS

class40ants-ci/jobs/docs:build-docs (lisp-job)

Builds documentation and uploads it to GitHub using "40ants/build-docs" github action.

Readers

reader40ants-ci/jobs/docs:error-on-warnings (build-docs) (:error-on-warnings = t)

Functions

function40ants-ci/jobs/docs:build-docs &rest args &key (error-on-warnings t) os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp asdf-system qlfile checkout-submodules dynamic-space-size

Creates a job of class build-docs.

40ANTS-CI/JOBS/JOB

package40ants-ci/jobs/job

Classes

JOB

class40ants-ci/jobs/job:job ()

Readers

reader40ants-ci/jobs/job:exclude (job) (:exclude = nil)

A list of plists denoting matrix combinations to be excluded.

reader40ants-ci/jobs/job:explicit-steps (job) (:steps = nil)

This slot holds steps given as a STEPS argument to a job constructor. Depending on a job class, it might add additional steps around these explicit steps.

reader40ants-ci/jobs/job:job-env (job) (:env = nil)

An alist of environment variables and their values to be added on job level. Values are evaluated in runtime.

reader40ants-ci/jobs/job:name (job) (:name)

If this name was not given in constructor, then name will be lowercased name of the job class.

reader40ants-ci/jobs/job:os (job) (:OS = "ubuntu-latest")

reader40ants-ci/jobs/job:permissions (job) (:permissions = nil)

A plist of permissions need for running the job.

These permissions will be bound to secrets.GITHUB_TOKEN variable. Use default-initargs to override permissions in subclasses:

(:default-initargs:permissions'(:content"write"))

reader40ants-ci/jobs/job:steps-after (job) (:steps-after = nil)

This slot holds steps given as a STEPS-AFTER argument to a job constructor. These steps will be appended to steps returned by the job class.

reader40ants-ci/jobs/job:steps-before (job) (:steps-before = nil)

This slot holds steps given as a STEPS-BEFORE argument to a job constructor. These steps will be prepended to steps returned by the job class.

Generics

generic-function40ants-ci/jobs/job:make-env job

generic-function40ants-ci/jobs/job:make-matrix job

generic-function40ants-ci/jobs/job:make-permissions job

Should return an alist with mapping from string to string where keys are scopes and values are permission names. Default method generates this alist from the plist of job's "permissions" slot.

generic-function40ants-ci/jobs/job:steps job

generic-function40ants-ci/jobs/job:use-matrix-p job

40ANTS-CI/JOBS/LINTER

package40ants-ci/jobs/linter

Classes

LINTER

class40ants-ci/jobs/linter:linter (lisp-job)

Readers

reader40ants-ci/jobs/linter:asdf-systems (linter) (:asdf-systems = nil)

Linter can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/linter:check-imports (linter) (:check-imports = nil)

Linter will check for missing or unused imports of package-inferred systems.

Functions

function40ants-ci/jobs/linter:linter &rest args &key asdf-systems check-imports os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp qlfile checkout-submodules dynamic-space-size

Creates a job which will run SBLint for given ASDF systems.

If no ASD files given, it will use all ASD files from the current ASDF system.

40ANTS-CI/JOBS/LISP-JOB

package40ants-ci/jobs/lisp-job

Classes

LISP-JOB

class40ants-ci/jobs/lisp-job:lisp-job (job)

This job checkouts the sources, installs Roswell and Qlot. Also, it caches results between runs.

Readers

reader40ants-ci/jobs/lisp-job:asdf-system (lisp-job) (:asdf-system = nil)

reader40ants-ci/jobs/lisp-job:asdf-version (lisp-job) (:asdf-version = nil)

ASDF version to use when setting up Lisp environment. If NIL, then the latest will be used.

reader40ants-ci/jobs/lisp-job:checkout-submodules (lisp-job) (:checkout-submodules = nil)

If this flag is true, then we will command actions/checkout action to checkout submodules.

reader40ants-ci/jobs/lisp-job:dynamic-space-size (lisp-job) (:dynamic-space-size = nil)

Dynamic space size for SBCL.

reader40ants-ci/jobs/lisp-job:lisp (lisp-job) (:LISP = "sbcl-bin")

reader40ants-ci/jobs/lisp-job:qlfile (lisp-job) (:qlfile = nil)

reader40ants-ci/jobs/lisp-job:qlot-version (lisp-job) (:qlot-version = nil)

Qlot version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

reader40ants-ci/jobs/lisp-job:quicklisp (lisp-job) (:QUICKLISP = "quicklisp")

reader40ants-ci/jobs/lisp-job:roswell-version (lisp-job) (:roswell-version = nil)

Roswell version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

40ANTS-CI/JOBS/RUN-TESTS

package40ants-ci/jobs/run-tests

Classes

RUN-TESTS

class40ants-ci/jobs/run-tests:run-tests (lisp-job)

This job test runs tests for a given ASDF system.

Readers

reader40ants-ci/jobs/run-tests:coverage (run-tests) (:coverage = nil)

reader40ants-ci/jobs/run-tests:custom (run-tests) (:custom = nil)

Functions

function40ants-ci/jobs/run-tests:run-tests &rest rest &key coverage custom os permissions steps steps-before steps-after env roswell-version asdf-version qlot-version lisp exclude qlfile quicklisp asdf-system checkout-submodules dynamic-space-size

Creates a job step of class run-tests.

40ANTS-CI/STEPS/ACTION

package40ants-ci/steps/action

Classes

ACTION

class40ants-ci/steps/action:action (step)

Readers

reader40ants-ci/steps/action:action-args (action) (:args)

A plist to be passed as "with" dictionary to the action.

reader40ants-ci/steps/action:uses (action) (:uses)

Functions

function40ants-ci/steps/action:action name uses &rest args &key id if env &allow-other-keys

40ANTS-CI/STEPS/SH

package40ants-ci/steps/sh

Classes

SH

class40ants-ci/steps/sh:sh (step)

Readers

reader40ants-ci/steps/sh:command (sh) (:command)

reader40ants-ci/steps/sh:shell (sh) (:shell = *default-shell*)

Functions

function40ants-ci/steps/sh:sh name command &key id if (shell *default-shell*) env

Macros

macro40ants-ci/steps/sh:sections &body body

Returns a string with a bash script where some parts are grouped.

In this example we have 3 sections:

(sections
("Help Argument""qlot exec cl-info --help")
("Version Argument""qlot exec cl-info --version")
("Lisp Systems Info""qlot exec cl-info""qlot exec cl-info cl-info defmain"))

It will be compiled into:

echo ::group::Help Argument
qlot exec cl-info --help
echo ::endgroup::
echo ::group::Version Argument
qlot exec cl-info --version
echo ::endgroup::
echo ::group::Lisp Systems Info
qlot exec cl-info
qlot exec cl-info cl-info defmain
echo ::endgroup::

40ANTS-CI/STEPS/STEP

package40ants-ci/steps/step

Classes

STEP

class40ants-ci/steps/step:step ()

Readers

reader40ants-ci/steps/step:env (step) (:env = nil)

An alist of environment variables.

reader40ants-ci/steps/step:step-id (step) (:id = nil)

reader40ants-ci/steps/step:step-if (step) (:if = nil)

reader40ants-ci/steps/step:step-name (step) (:name = nil)

40ANTS-CI/UTILS

package40ants-ci/utils

Generics

generic-function40ants-ci/utils:system-packages system

Returns a list of packages created by ASDF system.

Default implementation returns a package having the same name as a system and all packages matched to package-inferred subsystems:

CL-USER> (docs-builder/utils:system-packages :docs-builder)
(#<PACKAGE "DOCS-BUILDER">
#<PACKAGE "DOCS-BUILDER/UTILS">
#<PACKAGE "DOCS-BUILDER/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/GENEVA/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/GUESSER">
#<PACKAGE "DOCS-BUILDER/DOCS">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/BUILDER">)

Functions

function40ants-ci/utils:alistp list

Test wheather LIST argument is a properly formed alist.

In this library, alist has always a string as a key. Because we need them to have this form to serialize to JSON propertly.

(alistp '(("cron" . "0 10 * * 1"))) -> T (alistp '((("cron" . "0 10 * * 1")))) -> NIL

function40ants-ci/utils:current-system-name

function40ants-ci/utils:dedent text

Removes common leading whitespace from each string.

A few examples:

(dedent "Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "
Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD.")
"This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD."

function40ants-ci/utils:ensure-list-of-plists data

function40ants-ci/utils:ensure-primary-system system

function40ants-ci/utils:make-github-workflows-path system

function40ants-ci/utils:plist-to-alist plist &key (string-keys t) (lowercase t)

Make an alist from a plist PLIST.

By default, transforms keys to lowercased strings

function40ants-ci/utils:plistp list

Test wheather LIST is a properly formed plist.

function40ants-ci/utils:single list

Test wheather LIST contains exactly 1 element.

function40ants-ci/utils:to-json data

40ANTS-CI/VARS

package40ants-ci/vars

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

variable40ants-ci/vars:*use-cache* nil

Workflow will set this variable when preparing the data or YAML generation.

40ANTS-CI/WORKFLOW

package40ants-ci/workflow

Classes

WORKFLOW

class40ants-ci/workflow:workflow ()

Readers

reader40ants-ci/workflow:by-cron (workflow) (:by-cron = "0 10 * * 1")

reader40ants-ci/workflow:cache-p (workflow) (:cache = t)

reader40ants-ci/workflow:jobs (workflow) (:jobs = nil)

reader40ants-ci/workflow:name (workflow) (:name)

reader40ants-ci/workflow:on-pull-request (workflow) (:on-pull-request = t)

reader40ants-ci/workflow:on-push-to (workflow) (:ON-PUSH-TO = "master")

reader40ants-ci/workflow:workflow-env (workflow) (:env = nil)

An alist of environment variables and their values to be added on workflow level. Values are evaluated in runtime.

Macros

macro40ants-ci/workflow:defworkflow name &key on-push-to by-cron on-pull-request cache env jobs

[generated by 40ANTS-DOC]

About

Highly opionated Github Actions workflow builder for Common Lisp projects.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

40Ants-CI - Github Workflow Generator

This is a small utility, which can generate GitHub workflows for Common Lisp projects.

It generates workflow for running tests and building docs. These workflows use 40ants/run-tests and 40ants/build-docs actions and SBLint to check code for compilation errors.

40ANTS-CI ASDF System Details

Reasons to Use

  • This system hides all entrails related to caching.
  • Includes a few ready to use job types.
  • Custom job types can be defined and distributed as separate ASDF systems.
  • You don't have to write YAML anymore!

Quickstart

This system allows you to define workflows in the lisp code. The best way is to make these definitions a part of your ASDF system. This way 40ants-ci (12) will be able to automatically understand for which system it builds a workflow.

Each workflow consists of jobs and each job is a number of steps.

There are three predefine types of jobs and you can create your own. Predefined jobs allows to reuse steps in multiple CL libraries.

In next examples, I'll presume you are writing code in a file which is the part of the package inferred ASDF system EXAMPLE/CI. A file should have the following header:

(defpackage#:example/ci
(:use#:cl)
(:import-from#:40ants-ci/workflow
#:defworkflow)
(:import-from#:40ants-ci/jobs/linter)
(:import-from#:40ants-ci/jobs/run-tests)
(:import-from#:40ants-ci/jobs/docs))

Job Types

Autotag

This job is automates git tag placement on the commit where you have changed the ChangeLog.md.

This can be a useful to automate package deployment and releases. You update the changelog, a job pushes a new git tag and the next action triggers on this tag and build a release.

Or you if you publish your library at Quicklisp distribution, then you can change it's source type to the latest-github-tag to provide more stable releases to your users. This way you commits into master will be ignored until you change the changelog and git tag will be pushed. Here is an example how to setup this kind of quicklisp project source.

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag)))

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Linter

The simplest job type is linter. It loads a

(defworkflow linter
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)))

When you'll hit C-c C-c on this definition, it will generate .github/workflows/linter.yml with following content:

{
"name": "LINTER",
"on": {
"pull_request": null
},
"jobs": {
"linter": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Install SBLint",
"run": "qlot exec ros install cxxxr/sblint",
"shell": "bash"
},
{
"name": "Run Linter",
"run": "qlot exec sblint example.asd",
"shell": "bash"
}
]
}
}
}

Here you can see, a few steps in the job:

  1. Checkout the code.
  2. Install Roswell & Qlot using 40ants/setup-lisp action.
  3. Install SBLint.
  4. Run linter for example.asd.

Another interesting thing is that this workflow automatically uses ubuntu-latestOS, Quicklisp and sbcl-bin Lisp implementation. Later I'll show you how to redefine these settings.

class40ants-ci/jobs/linter:linter (lisp-job)

Critic

This job is similar to linter, but instead of SBLint it runs Lisp Critic.

Lisp Critic is a program which advices how to make you Common Lisp code more idiomatic, readable and performant. Also, sometimes it might catch logical errors in the code.

Here is how you can add this job type in your workflow:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/critic:critic)))

Also, you might combine this job together with others, for example, with linter:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)
(40ants-ci/jobs/critic:critic)))

and they will be executed in parallel. See docs on 40ants-ci/jobs/critic:critic function to learn about supported arguments.

Running Tests

Another interesting job type is 40ants-ci/jobs/run-tests:run-tests (12).

When using this job type, make sure, your system runs tests on (ASDF:TEST-SYSTEM :system-name) call and signals error if something went wrong.

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:jobs ((40ants-ci/jobs/run-tests:run-tests
:coveraget)))

Here I've added a few options to the workflow:

  • by-cron - sets a schedule.
  • on-push-to - defines a branch or branches to track.

It will generate .github/workflows/ci.yml with following content:

{
"name": "CI",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": null,
"schedule": [
{
"cron": "0 10 * * 1"
}
]
},
"jobs": {
"run-tests": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Run Tests",
"uses": "40ants/run-tests@v2",
"with": {
"asdf-system": "example",
"coveralls-token": "${{ secrets.github_token }}"
}
}
]
}
}
}

The result is similar to the workflow generated for Linter, but uses 40ants/setup-lisp action at the final step.

Also, I've passed an option :coverage t to the job. Thus coverage report will be uploaded to Coveralls.io automatically.

Defining a test Matrix

Lisp has many implementations and can be used on multiple platforms. Thus it is a good idea to test our software on many combinations of OS and lisp implementations. Workflow generator makes this very easy.

Here is an example of workflow definition with three dimentional matrix. It not only tests a library under different lisps and OS, but also checks if it works with the latest Quicklisp and Ultralisp distributions:

(defworkflow ci
:on-pull-requestt:jobs ((run-tests
:os ("ubuntu-latest""macos-latest")
:quicklisp ("quicklisp""ultralisp")
:lisp ("sbcl-bin""ccl-bin""allegro""clisp""cmucl")
:exclude (;; Seems allegro is does not support 64bit OSX.;; Unable to install it using Roswell:;; alisp is not executable. Missing 32bit glibc?
(:os"macos-latest":lisp"allegro")))))

Multiple jobs

Besides a build matrix, you might specify a multiple jobs of the same type, but with different parameters:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:lisp"sbcl-bin")
(run-tests
:lisp"ccl-bin")
(run-tests
:lisp"allegro")))

This will generate a workflow with three jobs: "run-tests", "run-tests-2" and "run-tests-3".

Meaningful names might be specified as well:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:name"test-on-sbcl":lisp"sbcl-bin")
(run-tests
:name"test-on-ccl":lisp"ccl-bin")
(run-tests
:name"test-on-allegro":lisp"allegro")))

Here is how these jobs will look like in the GitHub interface:

Building Docs

Third predefined job type is 40ants-ci/jobs/docs:build-docs (12). It uses 40ants/build-docs action and will work only if your ASDF system uses a documentation builder supported by 40ants/docs-builder.

To build docs on every push to master, just use this code:

(defworkflow docs
:on-push-to"master":jobs ((40ants-ci/jobs/docs:build-docs)))

It will generate .github/workflows/docs.yml with following content:

{
"name": "DOCS",
"on": {
"push": {
"branches": [
"master"
]
}
},
"jobs": {
"build-docs": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example",
"qlfile-template": ""
}
},
{
"name": "Build Docs",
"uses": "40ants/build-docs@v1",
"with": {
"asdf-system": "example"
}
}
]
}
}
}

Caching

To significantly speed up our tests, we can cache installed Roswell, Qlot and Common Lisp fasl files.

To accomplish this task, you don't need to dig into GitHub's docs anymore! Just add one line :cache t to your workflow definition:

(defworkflow docs
:on-push-to"master":cachet:jobs ((40ants-ci/jobs/docs:build-docs)))

Here is the diff of the generated workflow file. It shows steps, added automatically:

modified .github/workflows/docs.yml
@@ -20,13 +20,40 @@
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
+ {+ "name": "Grant All Perms to Make Cache Restoring Possible",+ "run": "sudo mkdir -p /usr/local/etc/roswell\n sudo chown \"${USER}\" /usr/local/etc/roswell\n # Here the ros binary will be restored:\n sudo chown \"${USER}\" /usr/local/bin",+ "shell": "bash"+ },+ {+ "name": "Get Current Month",+ "id": "current-month",+ "run": "echo \"::set-output name=value::$(date -u \"+%Y-%m\")\"",+ "shell": "bash"+ },+ {+ "name": "Cache Roswell Setup",+ "id": "cache",+ "uses": "actions/cache@v3",+ "with": {+ "path": "qlfile\n qlfile.lock\n /usr/local/bin/ros\n ~/.cache/common-lisp/\n ~/.roswell\n /usr/local/etc/roswell\n .qlot",+ "key": "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-ubuntu-latest-quicklisp-sbcl-bin-${{ hashFiles('qlfile.lock') }}"+ }+ },+ {+ "name": "Restore Path To Cached Files",+ "run": "echo $HOME/.roswell/bin >> $GITHUB_PATH\n echo .qlot/bin >> $GITHUB_PATH",+ "shell": "bash",+ "if": "steps.cache.outputs.cache-hit == 'true'"+ },
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "40ants-ci",
"qlfile-template": ""
- }+ },+ "if": "steps.cache.outputs.cache-hit != 'true'"
},
{

Adding env variables

You can specify additional environment variables on any level of the GitHub workflow: for workflow itself, for a job or for a step.

To specify env for workflow or a job, just add an ENV argument with alist or plist value like this:

(defworkflow release
:on-push-to"master":env (:github-token"${{ secrets.autotag_token }}")
:jobs ((40ants-ci/jobs/autotag:autotag)))

or as alist:

(defworkflow release
:on-push-to"master":env (("github_token"."${{ secrets.autotag_token }}"))
:jobs ((40ants-ci/jobs/autotag:autotag)))

or for the job itself:

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag
:env (:github-token"${{ secrets.autotag_token }}"))))

the same way it can be specified on a custom step:

(40ants-ci/steps/sh:sh "Custom env-var example""echo $CUSTOM_VAR":env (:custom-var"Hello world!"))

Note - environment variable names are always transformed to uppercase and dashes are replaced with underscores.

Running custom steps

Sometimes you might need to install custom system packages or do something before the job will finish. To accomplish these task you can provide custom steps using BEFORE-STEPS argument or AFTER-STEPS argument.

Here is an example where we are installing system package libunaq1-dev before running the testsuite:

(defparameter*required-steps*
(list (sh "Install libunac""sudo apt-get install -y libunac1-dev")))
(defworkflow ci
:on-pull-requestt:cachet:jobs ((run-tests
:steps-before*required-steps*:asdf-system"my-asdf-system")))

Details

TODO: I have to write a few chapters with details on additional job's parameters and a way how to create new job types.

But for now, I want to show a small example, how to define a workflow with a job which takes care about lisp installation and then calls a custom step:

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:cachet:jobs ((40ants-ci/jobs/lisp-job:lisp-job :name"check-ros-config":lisp"ccl-bin":steps ((40ants-ci/steps/sh:sh "Show Roswell Config""ros config")))))

Here we are using the class 40ants-ci/jobs/lisp-job:lisp-job which is base for most classes in this ASDF system and pass a custom 40ants-ci/steps/sh:sh (12) step to it. This step will be called after the repostory checkout and CCL-BIN lisp installation. so, thus when this step will run ros config command, it will output something like that:

asdf.version=3.3.5.3
ccl-bin.version=1.12.2
setup.time=3918000017
sbcl-bin.version=2.4.1
default.lisp=ccl-bin
Possible subcommands:
set
show

Pay attention to the NAME argument of 40ants-ci/jobs/lisp-job:lisp-job class. If you omit it, then default "lisp-job" name will be used.

API

40ANTS-CI

package40ants-ci

Functions

function40ants-ci:generate system &key path

Generates GitHub workflow for given ASDF system.

This function searches workflow definitions in all packages of the given ASDF system.

If PATH argument is not given, workflow files will be written to .github/workflow/ relarive to the SYSTEM.

40ANTS-CI/GITHUB

package40ants-ci/github

Generics

generic-function40ants-ci/github:generate obj path

generic-function40ants-ci/github:prepare-data obj

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

40ANTS-CI/JOBS/AUTOTAG

package40ants-ci/jobs/autotag

Classes

AUTOTAG

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Readers

reader40ants-ci/jobs/autotag:filename (autotag) (:filename = *default-filename*)

File where to search for version numbers.

reader40ants-ci/jobs/autotag:regex (autotag) (:regex = *default-regex*)

Regexp used to extract version numbers.

reader40ants-ci/jobs/autotag:tag-prefix (autotag) (:tag-prefix = *default-tag-prefix*)

Tag prefix.

reader40ants-ci/jobs/autotag:token-pattern (autotag) (:token-pattern = *default-token-pattern*)

Auth token pattern.

Functions

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

40ANTS-CI/JOBS/CRITIC

package40ants-ci/jobs/critic

Classes

CRITIC

class40ants-ci/jobs/critic:critic (lisp-job)

Readers

reader40ants-ci/jobs/critic:asdf-systems (critic) (:asdf-systems)

Critic can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/critic:ignore-critiques (critic) (:ignore-critiques)

A list strigns with names of critiques to ignore.

Functions

function40ants-ci/jobs/critic:critic &key asdf-systems asdf-version ignore-critiques env

Creates a job which will run Lisp Critic for given ASDF systems.

If argument ASDF-SYSTEMS is NIL, it will use ASDF system to which current lisp file is belong.

You may also provide ASDF-VERSION argument. It should be a string. By default, the latest ASDF version will be used.

40ANTS-CI/JOBS/DOCS

package40ants-ci/jobs/docs

Classes

BUILD-DOCS

class40ants-ci/jobs/docs:build-docs (lisp-job)

Builds documentation and uploads it to GitHub using "40ants/build-docs" github action.

Readers

reader40ants-ci/jobs/docs:error-on-warnings (build-docs) (:error-on-warnings = t)

Functions

function40ants-ci/jobs/docs:build-docs &rest args &key (error-on-warnings t) os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp asdf-system qlfile checkout-submodules dynamic-space-size

Creates a job of class build-docs.

40ANTS-CI/JOBS/JOB

package40ants-ci/jobs/job

Classes

JOB

class40ants-ci/jobs/job:job ()

Readers

reader40ants-ci/jobs/job:exclude (job) (:exclude = nil)

A list of plists denoting matrix combinations to be excluded.

reader40ants-ci/jobs/job:explicit-steps (job) (:steps = nil)

This slot holds steps given as a STEPS argument to a job constructor. Depending on a job class, it might add additional steps around these explicit steps.

reader40ants-ci/jobs/job:job-env (job) (:env = nil)

An alist of environment variables and their values to be added on job level. Values are evaluated in runtime.

reader40ants-ci/jobs/job:name (job) (:name)

If this name was not given in constructor, then name will be lowercased name of the job class.

reader40ants-ci/jobs/job:os (job) (:OS = "ubuntu-latest")

reader40ants-ci/jobs/job:permissions (job) (:permissions = nil)

A plist of permissions need for running the job.

These permissions will be bound to secrets.GITHUB_TOKEN variable. Use default-initargs to override permissions in subclasses:

(:default-initargs:permissions'(:content"write"))

reader40ants-ci/jobs/job:steps-after (job) (:steps-after = nil)

This slot holds steps given as a STEPS-AFTER argument to a job constructor. These steps will be appended to steps returned by the job class.

reader40ants-ci/jobs/job:steps-before (job) (:steps-before = nil)

This slot holds steps given as a STEPS-BEFORE argument to a job constructor. These steps will be prepended to steps returned by the job class.

Generics

generic-function40ants-ci/jobs/job:make-env job

generic-function40ants-ci/jobs/job:make-matrix job

generic-function40ants-ci/jobs/job:make-permissions job

Should return an alist with mapping from string to string where keys are scopes and values are permission names. Default method generates this alist from the plist of job's "permissions" slot.

generic-function40ants-ci/jobs/job:steps job

generic-function40ants-ci/jobs/job:use-matrix-p job

40ANTS-CI/JOBS/LINTER

package40ants-ci/jobs/linter

Classes

LINTER

class40ants-ci/jobs/linter:linter (lisp-job)

Readers

reader40ants-ci/jobs/linter:asdf-systems (linter) (:asdf-systems = nil)

Linter can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/linter:check-imports (linter) (:check-imports = nil)

Linter will check for missing or unused imports of package-inferred systems.

Functions

function40ants-ci/jobs/linter:linter &rest args &key asdf-systems check-imports os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp qlfile checkout-submodules dynamic-space-size

Creates a job which will run SBLint for given ASDF systems.

If no ASD files given, it will use all ASD files from the current ASDF system.

40ANTS-CI/JOBS/LISP-JOB

package40ants-ci/jobs/lisp-job

Classes

LISP-JOB

class40ants-ci/jobs/lisp-job:lisp-job (job)

This job checkouts the sources, installs Roswell and Qlot. Also, it caches results between runs.

Readers

reader40ants-ci/jobs/lisp-job:asdf-system (lisp-job) (:asdf-system = nil)

reader40ants-ci/jobs/lisp-job:asdf-version (lisp-job) (:asdf-version = nil)

ASDF version to use when setting up Lisp environment. If NIL, then the latest will be used.

reader40ants-ci/jobs/lisp-job:checkout-submodules (lisp-job) (:checkout-submodules = nil)

If this flag is true, then we will command actions/checkout action to checkout submodules.

reader40ants-ci/jobs/lisp-job:dynamic-space-size (lisp-job) (:dynamic-space-size = nil)

Dynamic space size for SBCL.

reader40ants-ci/jobs/lisp-job:lisp (lisp-job) (:LISP = "sbcl-bin")

reader40ants-ci/jobs/lisp-job:qlfile (lisp-job) (:qlfile = nil)

reader40ants-ci/jobs/lisp-job:qlot-version (lisp-job) (:qlot-version = nil)

Qlot version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

reader40ants-ci/jobs/lisp-job:quicklisp (lisp-job) (:QUICKLISP = "quicklisp")

reader40ants-ci/jobs/lisp-job:roswell-version (lisp-job) (:roswell-version = nil)

Roswell version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

40ANTS-CI/JOBS/RUN-TESTS

package40ants-ci/jobs/run-tests

Classes

RUN-TESTS

class40ants-ci/jobs/run-tests:run-tests (lisp-job)

This job test runs tests for a given ASDF system.

Readers

reader40ants-ci/jobs/run-tests:coverage (run-tests) (:coverage = nil)

reader40ants-ci/jobs/run-tests:custom (run-tests) (:custom = nil)

Functions

function40ants-ci/jobs/run-tests:run-tests &rest rest &key coverage custom os permissions steps steps-before steps-after env roswell-version asdf-version qlot-version lisp exclude qlfile quicklisp asdf-system checkout-submodules dynamic-space-size

Creates a job step of class run-tests.

40ANTS-CI/STEPS/ACTION

package40ants-ci/steps/action

Classes

ACTION

class40ants-ci/steps/action:action (step)

Readers

reader40ants-ci/steps/action:action-args (action) (:args)

A plist to be passed as "with" dictionary to the action.

reader40ants-ci/steps/action:uses (action) (:uses)

Functions

function40ants-ci/steps/action:action name uses &rest args &key id if env &allow-other-keys

40ANTS-CI/STEPS/SH

package40ants-ci/steps/sh

Classes

SH

class40ants-ci/steps/sh:sh (step)

Readers

reader40ants-ci/steps/sh:command (sh) (:command)

reader40ants-ci/steps/sh:shell (sh) (:shell = *default-shell*)

Functions

function40ants-ci/steps/sh:sh name command &key id if (shell *default-shell*) env

Macros

macro40ants-ci/steps/sh:sections &body body

Returns a string with a bash script where some parts are grouped.

In this example we have 3 sections:

(sections
("Help Argument""qlot exec cl-info --help")
("Version Argument""qlot exec cl-info --version")
("Lisp Systems Info""qlot exec cl-info""qlot exec cl-info cl-info defmain"))

It will be compiled into:

echo ::group::Help Argument
qlot exec cl-info --help
echo ::endgroup::
echo ::group::Version Argument
qlot exec cl-info --version
echo ::endgroup::
echo ::group::Lisp Systems Info
qlot exec cl-info
qlot exec cl-info cl-info defmain
echo ::endgroup::

40ANTS-CI/STEPS/STEP

package40ants-ci/steps/step

Classes

STEP

class40ants-ci/steps/step:step ()

Readers

reader40ants-ci/steps/step:env (step) (:env = nil)

An alist of environment variables.

reader40ants-ci/steps/step:step-id (step) (:id = nil)

reader40ants-ci/steps/step:step-if (step) (:if = nil)

reader40ants-ci/steps/step:step-name (step) (:name = nil)

40ANTS-CI/UTILS

package40ants-ci/utils

Generics

generic-function40ants-ci/utils:system-packages system

Returns a list of packages created by ASDF system.

Default implementation returns a package having the same name as a system and all packages matched to package-inferred subsystems:

CL-USER> (docs-builder/utils:system-packages :docs-builder)
(#<PACKAGE "DOCS-BUILDER">
#<PACKAGE "DOCS-BUILDER/UTILS">
#<PACKAGE "DOCS-BUILDER/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/GENEVA/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/GUESSER">
#<PACKAGE "DOCS-BUILDER/DOCS">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/BUILDER">)

Functions

function40ants-ci/utils:alistp list

Test wheather LIST argument is a properly formed alist.

In this library, alist has always a string as a key. Because we need them to have this form to serialize to JSON propertly.

(alistp '(("cron" . "0 10 * * 1"))) -> T (alistp '((("cron" . "0 10 * * 1")))) -> NIL

function40ants-ci/utils:current-system-name

function40ants-ci/utils:dedent text

Removes common leading whitespace from each string.

A few examples:

(dedent "Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "
Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD.")
"This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD."

function40ants-ci/utils:ensure-list-of-plists data

function40ants-ci/utils:ensure-primary-system system

function40ants-ci/utils:make-github-workflows-path system

function40ants-ci/utils:plist-to-alist plist &key (string-keys t) (lowercase t)

Make an alist from a plist PLIST.

By default, transforms keys to lowercased strings

function40ants-ci/utils:plistp list

Test wheather LIST is a properly formed plist.

function40ants-ci/utils:single list

Test wheather LIST contains exactly 1 element.

function40ants-ci/utils:to-json data

40ANTS-CI/VARS

package40ants-ci/vars

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

variable40ants-ci/vars:*use-cache* nil

Workflow will set this variable when preparing the data or YAML generation.

40ANTS-CI/WORKFLOW

package40ants-ci/workflow

Classes

WORKFLOW

class40ants-ci/workflow:workflow ()

Readers

reader40ants-ci/workflow:by-cron (workflow) (:by-cron = "0 10 * * 1")

reader40ants-ci/workflow:cache-p (workflow) (:cache = t)

reader40ants-ci/workflow:jobs (workflow) (:jobs = nil)

reader40ants-ci/workflow:name (workflow) (:name)

reader40ants-ci/workflow:on-pull-request (workflow) (:on-pull-request = t)

reader40ants-ci/workflow:on-push-to (workflow) (:ON-PUSH-TO = "master")

reader40ants-ci/workflow:workflow-env (workflow) (:env = nil)

An alist of environment variables and their values to be added on workflow level. Values are evaluated in runtime.

Macros

macro40ants-ci/workflow:defworkflow name &key on-push-to by-cron on-pull-request cache env jobs

[generated by 40ANTS-DOC]

About

Highly opionated Github Actions workflow builder for Common Lisp projects.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

40Ants-CI - Github Workflow Generator

This is a small utility, which can generate GitHub workflows for Common Lisp projects.

It generates workflow for running tests and building docs. These workflows use 40ants/run-tests and 40ants/build-docs actions and SBLint to check code for compilation errors.

40ANTS-CI ASDF System Details

Reasons to Use

  • This system hides all entrails related to caching.
  • Includes a few ready to use job types.
  • Custom job types can be defined and distributed as separate ASDF systems.
  • You don't have to write YAML anymore!

Quickstart

This system allows you to define workflows in the lisp code. The best way is to make these definitions a part of your ASDF system. This way 40ants-ci (12) will be able to automatically understand for which system it builds a workflow.

Each workflow consists of jobs and each job is a number of steps.

There are three predefine types of jobs and you can create your own. Predefined jobs allows to reuse steps in multiple CL libraries.

In next examples, I'll presume you are writing code in a file which is the part of the package inferred ASDF system EXAMPLE/CI. A file should have the following header:

(defpackage#:example/ci
(:use#:cl)
(:import-from#:40ants-ci/workflow
#:defworkflow)
(:import-from#:40ants-ci/jobs/linter)
(:import-from#:40ants-ci/jobs/run-tests)
(:import-from#:40ants-ci/jobs/docs))

Job Types

Autotag

This job is automates git tag placement on the commit where you have changed the ChangeLog.md.

This can be a useful to automate package deployment and releases. You update the changelog, a job pushes a new git tag and the next action triggers on this tag and build a release.

Or you if you publish your library at Quicklisp distribution, then you can change it's source type to the latest-github-tag to provide more stable releases to your users. This way you commits into master will be ignored until you change the changelog and git tag will be pushed. Here is an example how to setup this kind of quicklisp project source.

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag)))

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Linter

The simplest job type is linter. It loads a

(defworkflow linter
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)))

When you'll hit C-c C-c on this definition, it will generate .github/workflows/linter.yml with following content:

{
"name": "LINTER",
"on": {
"pull_request": null
},
"jobs": {
"linter": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Install SBLint",
"run": "qlot exec ros install cxxxr/sblint",
"shell": "bash"
},
{
"name": "Run Linter",
"run": "qlot exec sblint example.asd",
"shell": "bash"
}
]
}
}
}

Here you can see, a few steps in the job:

  1. Checkout the code.
  2. Install Roswell & Qlot using 40ants/setup-lisp action.
  3. Install SBLint.
  4. Run linter for example.asd.

Another interesting thing is that this workflow automatically uses ubuntu-latestOS, Quicklisp and sbcl-bin Lisp implementation. Later I'll show you how to redefine these settings.

class40ants-ci/jobs/linter:linter (lisp-job)

Critic

This job is similar to linter, but instead of SBLint it runs Lisp Critic.

Lisp Critic is a program which advices how to make you Common Lisp code more idiomatic, readable and performant. Also, sometimes it might catch logical errors in the code.

Here is how you can add this job type in your workflow:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/critic:critic)))

Also, you might combine this job together with others, for example, with linter:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)
(40ants-ci/jobs/critic:critic)))

and they will be executed in parallel. See docs on 40ants-ci/jobs/critic:critic function to learn about supported arguments.

Running Tests

Another interesting job type is 40ants-ci/jobs/run-tests:run-tests (12).

When using this job type, make sure, your system runs tests on (ASDF:TEST-SYSTEM :system-name) call and signals error if something went wrong.

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:jobs ((40ants-ci/jobs/run-tests:run-tests
:coveraget)))

Here I've added a few options to the workflow:

  • by-cron - sets a schedule.
  • on-push-to - defines a branch or branches to track.

It will generate .github/workflows/ci.yml with following content:

{
"name": "CI",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": null,
"schedule": [
{
"cron": "0 10 * * 1"
}
]
},
"jobs": {
"run-tests": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Run Tests",
"uses": "40ants/run-tests@v2",
"with": {
"asdf-system": "example",
"coveralls-token": "${{ secrets.github_token }}"
}
}
]
}
}
}

The result is similar to the workflow generated for Linter, but uses 40ants/setup-lisp action at the final step.

Also, I've passed an option :coverage t to the job. Thus coverage report will be uploaded to Coveralls.io automatically.

Defining a test Matrix

Lisp has many implementations and can be used on multiple platforms. Thus it is a good idea to test our software on many combinations of OS and lisp implementations. Workflow generator makes this very easy.

Here is an example of workflow definition with three dimentional matrix. It not only tests a library under different lisps and OS, but also checks if it works with the latest Quicklisp and Ultralisp distributions:

(defworkflow ci
:on-pull-requestt:jobs ((run-tests
:os ("ubuntu-latest""macos-latest")
:quicklisp ("quicklisp""ultralisp")
:lisp ("sbcl-bin""ccl-bin""allegro""clisp""cmucl")
:exclude (;; Seems allegro is does not support 64bit OSX.;; Unable to install it using Roswell:;; alisp is not executable. Missing 32bit glibc?
(:os"macos-latest":lisp"allegro")))))

Multiple jobs

Besides a build matrix, you might specify a multiple jobs of the same type, but with different parameters:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:lisp"sbcl-bin")
(run-tests
:lisp"ccl-bin")
(run-tests
:lisp"allegro")))

This will generate a workflow with three jobs: "run-tests", "run-tests-2" and "run-tests-3".

Meaningful names might be specified as well:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:name"test-on-sbcl":lisp"sbcl-bin")
(run-tests
:name"test-on-ccl":lisp"ccl-bin")
(run-tests
:name"test-on-allegro":lisp"allegro")))

Here is how these jobs will look like in the GitHub interface:

Building Docs

Third predefined job type is 40ants-ci/jobs/docs:build-docs (12). It uses 40ants/build-docs action and will work only if your ASDF system uses a documentation builder supported by 40ants/docs-builder.

To build docs on every push to master, just use this code:

(defworkflow docs
:on-push-to"master":jobs ((40ants-ci/jobs/docs:build-docs)))

It will generate .github/workflows/docs.yml with following content:

{
"name": "DOCS",
"on": {
"push": {
"branches": [
"master"
]
}
},
"jobs": {
"build-docs": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example",
"qlfile-template": ""
}
},
{
"name": "Build Docs",
"uses": "40ants/build-docs@v1",
"with": {
"asdf-system": "example"
}
}
]
}
}
}

Caching

To significantly speed up our tests, we can cache installed Roswell, Qlot and Common Lisp fasl files.

To accomplish this task, you don't need to dig into GitHub's docs anymore! Just add one line :cache t to your workflow definition:

(defworkflow docs
:on-push-to"master":cachet:jobs ((40ants-ci/jobs/docs:build-docs)))

Here is the diff of the generated workflow file. It shows steps, added automatically:

modified .github/workflows/docs.yml
@@ -20,13 +20,40 @@
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
+ {+ "name": "Grant All Perms to Make Cache Restoring Possible",+ "run": "sudo mkdir -p /usr/local/etc/roswell\n sudo chown \"${USER}\" /usr/local/etc/roswell\n # Here the ros binary will be restored:\n sudo chown \"${USER}\" /usr/local/bin",+ "shell": "bash"+ },+ {+ "name": "Get Current Month",+ "id": "current-month",+ "run": "echo \"::set-output name=value::$(date -u \"+%Y-%m\")\"",+ "shell": "bash"+ },+ {+ "name": "Cache Roswell Setup",+ "id": "cache",+ "uses": "actions/cache@v3",+ "with": {+ "path": "qlfile\n qlfile.lock\n /usr/local/bin/ros\n ~/.cache/common-lisp/\n ~/.roswell\n /usr/local/etc/roswell\n .qlot",+ "key": "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-ubuntu-latest-quicklisp-sbcl-bin-${{ hashFiles('qlfile.lock') }}"+ }+ },+ {+ "name": "Restore Path To Cached Files",+ "run": "echo $HOME/.roswell/bin >> $GITHUB_PATH\n echo .qlot/bin >> $GITHUB_PATH",+ "shell": "bash",+ "if": "steps.cache.outputs.cache-hit == 'true'"+ },
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "40ants-ci",
"qlfile-template": ""
- }+ },+ "if": "steps.cache.outputs.cache-hit != 'true'"
},
{

Adding env variables

You can specify additional environment variables on any level of the GitHub workflow: for workflow itself, for a job or for a step.

To specify env for workflow or a job, just add an ENV argument with alist or plist value like this:

(defworkflow release
:on-push-to"master":env (:github-token"${{ secrets.autotag_token }}")
:jobs ((40ants-ci/jobs/autotag:autotag)))

or as alist:

(defworkflow release
:on-push-to"master":env (("github_token"."${{ secrets.autotag_token }}"))
:jobs ((40ants-ci/jobs/autotag:autotag)))

or for the job itself:

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag
:env (:github-token"${{ secrets.autotag_token }}"))))

the same way it can be specified on a custom step:

(40ants-ci/steps/sh:sh "Custom env-var example""echo $CUSTOM_VAR":env (:custom-var"Hello world!"))

Note - environment variable names are always transformed to uppercase and dashes are replaced with underscores.

Running custom steps

Sometimes you might need to install custom system packages or do something before the job will finish. To accomplish these task you can provide custom steps using BEFORE-STEPS argument or AFTER-STEPS argument.

Here is an example where we are installing system package libunaq1-dev before running the testsuite:

(defparameter*required-steps*
(list (sh "Install libunac""sudo apt-get install -y libunac1-dev")))
(defworkflow ci
:on-pull-requestt:cachet:jobs ((run-tests
:steps-before*required-steps*:asdf-system"my-asdf-system")))

Details

TODO: I have to write a few chapters with details on additional job's parameters and a way how to create new job types.

But for now, I want to show a small example, how to define a workflow with a job which takes care about lisp installation and then calls a custom step:

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:cachet:jobs ((40ants-ci/jobs/lisp-job:lisp-job :name"check-ros-config":lisp"ccl-bin":steps ((40ants-ci/steps/sh:sh "Show Roswell Config""ros config")))))

Here we are using the class 40ants-ci/jobs/lisp-job:lisp-job which is base for most classes in this ASDF system and pass a custom 40ants-ci/steps/sh:sh (12) step to it. This step will be called after the repostory checkout and CCL-BIN lisp installation. so, thus when this step will run ros config command, it will output something like that:

asdf.version=3.3.5.3
ccl-bin.version=1.12.2
setup.time=3918000017
sbcl-bin.version=2.4.1
default.lisp=ccl-bin
Possible subcommands:
set
show

Pay attention to the NAME argument of 40ants-ci/jobs/lisp-job:lisp-job class. If you omit it, then default "lisp-job" name will be used.

API

40ANTS-CI

package40ants-ci

Functions

function40ants-ci:generate system &key path

Generates GitHub workflow for given ASDF system.

This function searches workflow definitions in all packages of the given ASDF system.

If PATH argument is not given, workflow files will be written to .github/workflow/ relarive to the SYSTEM.

40ANTS-CI/GITHUB

package40ants-ci/github

Generics

generic-function40ants-ci/github:generate obj path

generic-function40ants-ci/github:prepare-data obj

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

40ANTS-CI/JOBS/AUTOTAG

package40ants-ci/jobs/autotag

Classes

AUTOTAG

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Readers

reader40ants-ci/jobs/autotag:filename (autotag) (:filename = *default-filename*)

File where to search for version numbers.

reader40ants-ci/jobs/autotag:regex (autotag) (:regex = *default-regex*)

Regexp used to extract version numbers.

reader40ants-ci/jobs/autotag:tag-prefix (autotag) (:tag-prefix = *default-tag-prefix*)

Tag prefix.

reader40ants-ci/jobs/autotag:token-pattern (autotag) (:token-pattern = *default-token-pattern*)

Auth token pattern.

Functions

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

40ANTS-CI/JOBS/CRITIC

package40ants-ci/jobs/critic

Classes

CRITIC

class40ants-ci/jobs/critic:critic (lisp-job)

Readers

reader40ants-ci/jobs/critic:asdf-systems (critic) (:asdf-systems)

Critic can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/critic:ignore-critiques (critic) (:ignore-critiques)

A list strigns with names of critiques to ignore.

Functions

function40ants-ci/jobs/critic:critic &key asdf-systems asdf-version ignore-critiques env

Creates a job which will run Lisp Critic for given ASDF systems.

If argument ASDF-SYSTEMS is NIL, it will use ASDF system to which current lisp file is belong.

You may also provide ASDF-VERSION argument. It should be a string. By default, the latest ASDF version will be used.

40ANTS-CI/JOBS/DOCS

package40ants-ci/jobs/docs

Classes

BUILD-DOCS

class40ants-ci/jobs/docs:build-docs (lisp-job)

Builds documentation and uploads it to GitHub using "40ants/build-docs" github action.

Readers

reader40ants-ci/jobs/docs:error-on-warnings (build-docs) (:error-on-warnings = t)

Functions

function40ants-ci/jobs/docs:build-docs &rest args &key (error-on-warnings t) os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp asdf-system qlfile checkout-submodules dynamic-space-size

Creates a job of class build-docs.

40ANTS-CI/JOBS/JOB

package40ants-ci/jobs/job

Classes

JOB

class40ants-ci/jobs/job:job ()

Readers

reader40ants-ci/jobs/job:exclude (job) (:exclude = nil)

A list of plists denoting matrix combinations to be excluded.

reader40ants-ci/jobs/job:explicit-steps (job) (:steps = nil)

This slot holds steps given as a STEPS argument to a job constructor. Depending on a job class, it might add additional steps around these explicit steps.

reader40ants-ci/jobs/job:job-env (job) (:env = nil)

An alist of environment variables and their values to be added on job level. Values are evaluated in runtime.

reader40ants-ci/jobs/job:name (job) (:name)

If this name was not given in constructor, then name will be lowercased name of the job class.

reader40ants-ci/jobs/job:os (job) (:OS = "ubuntu-latest")

reader40ants-ci/jobs/job:permissions (job) (:permissions = nil)

A plist of permissions need for running the job.

These permissions will be bound to secrets.GITHUB_TOKEN variable. Use default-initargs to override permissions in subclasses:

(:default-initargs:permissions'(:content"write"))

reader40ants-ci/jobs/job:steps-after (job) (:steps-after = nil)

This slot holds steps given as a STEPS-AFTER argument to a job constructor. These steps will be appended to steps returned by the job class.

reader40ants-ci/jobs/job:steps-before (job) (:steps-before = nil)

This slot holds steps given as a STEPS-BEFORE argument to a job constructor. These steps will be prepended to steps returned by the job class.

Generics

generic-function40ants-ci/jobs/job:make-env job

generic-function40ants-ci/jobs/job:make-matrix job

generic-function40ants-ci/jobs/job:make-permissions job

Should return an alist with mapping from string to string where keys are scopes and values are permission names. Default method generates this alist from the plist of job's "permissions" slot.

generic-function40ants-ci/jobs/job:steps job

generic-function40ants-ci/jobs/job:use-matrix-p job

40ANTS-CI/JOBS/LINTER

package40ants-ci/jobs/linter

Classes

LINTER

class40ants-ci/jobs/linter:linter (lisp-job)

Readers

reader40ants-ci/jobs/linter:asdf-systems (linter) (:asdf-systems = nil)

Linter can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/linter:check-imports (linter) (:check-imports = nil)

Linter will check for missing or unused imports of package-inferred systems.

Functions

function40ants-ci/jobs/linter:linter &rest args &key asdf-systems check-imports os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp qlfile checkout-submodules dynamic-space-size

Creates a job which will run SBLint for given ASDF systems.

If no ASD files given, it will use all ASD files from the current ASDF system.

40ANTS-CI/JOBS/LISP-JOB

package40ants-ci/jobs/lisp-job

Classes

LISP-JOB

class40ants-ci/jobs/lisp-job:lisp-job (job)

This job checkouts the sources, installs Roswell and Qlot. Also, it caches results between runs.

Readers

reader40ants-ci/jobs/lisp-job:asdf-system (lisp-job) (:asdf-system = nil)

reader40ants-ci/jobs/lisp-job:asdf-version (lisp-job) (:asdf-version = nil)

ASDF version to use when setting up Lisp environment. If NIL, then the latest will be used.

reader40ants-ci/jobs/lisp-job:checkout-submodules (lisp-job) (:checkout-submodules = nil)

If this flag is true, then we will command actions/checkout action to checkout submodules.

reader40ants-ci/jobs/lisp-job:dynamic-space-size (lisp-job) (:dynamic-space-size = nil)

Dynamic space size for SBCL.

reader40ants-ci/jobs/lisp-job:lisp (lisp-job) (:LISP = "sbcl-bin")

reader40ants-ci/jobs/lisp-job:qlfile (lisp-job) (:qlfile = nil)

reader40ants-ci/jobs/lisp-job:qlot-version (lisp-job) (:qlot-version = nil)

Qlot version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

reader40ants-ci/jobs/lisp-job:quicklisp (lisp-job) (:QUICKLISP = "quicklisp")

reader40ants-ci/jobs/lisp-job:roswell-version (lisp-job) (:roswell-version = nil)

Roswell version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

40ANTS-CI/JOBS/RUN-TESTS

package40ants-ci/jobs/run-tests

Classes

RUN-TESTS

class40ants-ci/jobs/run-tests:run-tests (lisp-job)

This job test runs tests for a given ASDF system.

Readers

reader40ants-ci/jobs/run-tests:coverage (run-tests) (:coverage = nil)

reader40ants-ci/jobs/run-tests:custom (run-tests) (:custom = nil)

Functions

function40ants-ci/jobs/run-tests:run-tests &rest rest &key coverage custom os permissions steps steps-before steps-after env roswell-version asdf-version qlot-version lisp exclude qlfile quicklisp asdf-system checkout-submodules dynamic-space-size

Creates a job step of class run-tests.

40ANTS-CI/STEPS/ACTION

package40ants-ci/steps/action

Classes

ACTION

class40ants-ci/steps/action:action (step)

Readers

reader40ants-ci/steps/action:action-args (action) (:args)

A plist to be passed as "with" dictionary to the action.

reader40ants-ci/steps/action:uses (action) (:uses)

Functions

function40ants-ci/steps/action:action name uses &rest args &key id if env &allow-other-keys

40ANTS-CI/STEPS/SH

package40ants-ci/steps/sh

Classes

SH

class40ants-ci/steps/sh:sh (step)

Readers

reader40ants-ci/steps/sh:command (sh) (:command)

reader40ants-ci/steps/sh:shell (sh) (:shell = *default-shell*)

Functions

function40ants-ci/steps/sh:sh name command &key id if (shell *default-shell*) env

Macros

macro40ants-ci/steps/sh:sections &body body

Returns a string with a bash script where some parts are grouped.

In this example we have 3 sections:

(sections
("Help Argument""qlot exec cl-info --help")
("Version Argument""qlot exec cl-info --version")
("Lisp Systems Info""qlot exec cl-info""qlot exec cl-info cl-info defmain"))

It will be compiled into:

echo ::group::Help Argument
qlot exec cl-info --help
echo ::endgroup::
echo ::group::Version Argument
qlot exec cl-info --version
echo ::endgroup::
echo ::group::Lisp Systems Info
qlot exec cl-info
qlot exec cl-info cl-info defmain
echo ::endgroup::

40ANTS-CI/STEPS/STEP

package40ants-ci/steps/step

Classes

STEP

class40ants-ci/steps/step:step ()

Readers

reader40ants-ci/steps/step:env (step) (:env = nil)

An alist of environment variables.

reader40ants-ci/steps/step:step-id (step) (:id = nil)

reader40ants-ci/steps/step:step-if (step) (:if = nil)

reader40ants-ci/steps/step:step-name (step) (:name = nil)

40ANTS-CI/UTILS

package40ants-ci/utils

Generics

generic-function40ants-ci/utils:system-packages system

Returns a list of packages created by ASDF system.

Default implementation returns a package having the same name as a system and all packages matched to package-inferred subsystems:

CL-USER> (docs-builder/utils:system-packages :docs-builder)
(#<PACKAGE "DOCS-BUILDER">
#<PACKAGE "DOCS-BUILDER/UTILS">
#<PACKAGE "DOCS-BUILDER/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/GENEVA/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/GUESSER">
#<PACKAGE "DOCS-BUILDER/DOCS">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/BUILDER">)

Functions

function40ants-ci/utils:alistp list

Test wheather LIST argument is a properly formed alist.

In this library, alist has always a string as a key. Because we need them to have this form to serialize to JSON propertly.

(alistp '(("cron" . "0 10 * * 1"))) -> T (alistp '((("cron" . "0 10 * * 1")))) -> NIL

function40ants-ci/utils:current-system-name

function40ants-ci/utils:dedent text

Removes common leading whitespace from each string.

A few examples:

(dedent "Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "
Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD.")
"This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD."

function40ants-ci/utils:ensure-list-of-plists data

function40ants-ci/utils:ensure-primary-system system

function40ants-ci/utils:make-github-workflows-path system

function40ants-ci/utils:plist-to-alist plist &key (string-keys t) (lowercase t)

Make an alist from a plist PLIST.

By default, transforms keys to lowercased strings

function40ants-ci/utils:plistp list

Test wheather LIST is a properly formed plist.

function40ants-ci/utils:single list

Test wheather LIST contains exactly 1 element.

function40ants-ci/utils:to-json data

40ANTS-CI/VARS

package40ants-ci/vars

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

variable40ants-ci/vars:*use-cache* nil

Workflow will set this variable when preparing the data or YAML generation.

40ANTS-CI/WORKFLOW

package40ants-ci/workflow

Classes

WORKFLOW

class40ants-ci/workflow:workflow ()

Readers

reader40ants-ci/workflow:by-cron (workflow) (:by-cron = "0 10 * * 1")

reader40ants-ci/workflow:cache-p (workflow) (:cache = t)

reader40ants-ci/workflow:jobs (workflow) (:jobs = nil)

reader40ants-ci/workflow:name (workflow) (:name)

reader40ants-ci/workflow:on-pull-request (workflow) (:on-pull-request = t)

reader40ants-ci/workflow:on-push-to (workflow) (:ON-PUSH-TO = "master")

reader40ants-ci/workflow:workflow-env (workflow) (:env = nil)

An alist of environment variables and their values to be added on workflow level. Values are evaluated in runtime.

Macros

macro40ants-ci/workflow:defworkflow name &key on-push-to by-cron on-pull-request cache env jobs

[generated by 40ANTS-DOC]

About

Highly opionated Github Actions workflow builder for Common Lisp projects.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

40Ants-CI - Github Workflow Generator

This is a small utility, which can generate GitHub workflows for Common Lisp projects.

It generates workflow for running tests and building docs. These workflows use 40ants/run-tests and 40ants/build-docs actions and SBLint to check code for compilation errors.

40ANTS-CI ASDF System Details

Reasons to Use

  • This system hides all entrails related to caching.
  • Includes a few ready to use job types.
  • Custom job types can be defined and distributed as separate ASDF systems.
  • You don't have to write YAML anymore!

Quickstart

This system allows you to define workflows in the lisp code. The best way is to make these definitions a part of your ASDF system. This way 40ants-ci (12) will be able to automatically understand for which system it builds a workflow.

Each workflow consists of jobs and each job is a number of steps.

There are three predefine types of jobs and you can create your own. Predefined jobs allows to reuse steps in multiple CL libraries.

In next examples, I'll presume you are writing code in a file which is the part of the package inferred ASDF system EXAMPLE/CI. A file should have the following header:

(defpackage#:example/ci
(:use#:cl)
(:import-from#:40ants-ci/workflow
#:defworkflow)
(:import-from#:40ants-ci/jobs/linter)
(:import-from#:40ants-ci/jobs/run-tests)
(:import-from#:40ants-ci/jobs/docs))

Job Types

Autotag

This job is automates git tag placement on the commit where you have changed the ChangeLog.md.

This can be a useful to automate package deployment and releases. You update the changelog, a job pushes a new git tag and the next action triggers on this tag and build a release.

Or you if you publish your library at Quicklisp distribution, then you can change it's source type to the latest-github-tag to provide more stable releases to your users. This way you commits into master will be ignored until you change the changelog and git tag will be pushed. Here is an example how to setup this kind of quicklisp project source.

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag)))

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Linter

The simplest job type is linter. It loads a

(defworkflow linter
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)))

When you'll hit C-c C-c on this definition, it will generate .github/workflows/linter.yml with following content:

{
"name": "LINTER",
"on": {
"pull_request": null
},
"jobs": {
"linter": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Install SBLint",
"run": "qlot exec ros install cxxxr/sblint",
"shell": "bash"
},
{
"name": "Run Linter",
"run": "qlot exec sblint example.asd",
"shell": "bash"
}
]
}
}
}

Here you can see, a few steps in the job:

  1. Checkout the code.
  2. Install Roswell & Qlot using 40ants/setup-lisp action.
  3. Install SBLint.
  4. Run linter for example.asd.

Another interesting thing is that this workflow automatically uses ubuntu-latestOS, Quicklisp and sbcl-bin Lisp implementation. Later I'll show you how to redefine these settings.

class40ants-ci/jobs/linter:linter (lisp-job)

Critic

This job is similar to linter, but instead of SBLint it runs Lisp Critic.

Lisp Critic is a program which advices how to make you Common Lisp code more idiomatic, readable and performant. Also, sometimes it might catch logical errors in the code.

Here is how you can add this job type in your workflow:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/critic:critic)))

Also, you might combine this job together with others, for example, with linter:

(defworkflow ci
:on-pull-requestt:jobs ((40ants-ci/jobs/linter:linter)
(40ants-ci/jobs/critic:critic)))

and they will be executed in parallel. See docs on 40ants-ci/jobs/critic:critic function to learn about supported arguments.

Running Tests

Another interesting job type is 40ants-ci/jobs/run-tests:run-tests (12).

When using this job type, make sure, your system runs tests on (ASDF:TEST-SYSTEM :system-name) call and signals error if something went wrong.

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:jobs ((40ants-ci/jobs/run-tests:run-tests
:coveraget)))

Here I've added a few options to the workflow:

  • by-cron - sets a schedule.
  • on-push-to - defines a branch or branches to track.

It will generate .github/workflows/ci.yml with following content:

{
"name": "CI",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": null,
"schedule": [
{
"cron": "0 10 * * 1"
}
]
},
"jobs": {
"run-tests": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example"
}
},
{
"name": "Run Tests",
"uses": "40ants/run-tests@v2",
"with": {
"asdf-system": "example",
"coveralls-token": "${{ secrets.github_token }}"
}
}
]
}
}
}

The result is similar to the workflow generated for Linter, but uses 40ants/setup-lisp action at the final step.

Also, I've passed an option :coverage t to the job. Thus coverage report will be uploaded to Coveralls.io automatically.

Defining a test Matrix

Lisp has many implementations and can be used on multiple platforms. Thus it is a good idea to test our software on many combinations of OS and lisp implementations. Workflow generator makes this very easy.

Here is an example of workflow definition with three dimentional matrix. It not only tests a library under different lisps and OS, but also checks if it works with the latest Quicklisp and Ultralisp distributions:

(defworkflow ci
:on-pull-requestt:jobs ((run-tests
:os ("ubuntu-latest""macos-latest")
:quicklisp ("quicklisp""ultralisp")
:lisp ("sbcl-bin""ccl-bin""allegro""clisp""cmucl")
:exclude (;; Seems allegro is does not support 64bit OSX.;; Unable to install it using Roswell:;; alisp is not executable. Missing 32bit glibc?
(:os"macos-latest":lisp"allegro")))))

Multiple jobs

Besides a build matrix, you might specify a multiple jobs of the same type, but with different parameters:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:lisp"sbcl-bin")
(run-tests
:lisp"ccl-bin")
(run-tests
:lisp"allegro")))

This will generate a workflow with three jobs: "run-tests", "run-tests-2" and "run-tests-3".

Meaningful names might be specified as well:

(defworkflow ci
:on-push-to"master":on-pull-requestt:jobs ((run-tests
:name"test-on-sbcl":lisp"sbcl-bin")
(run-tests
:name"test-on-ccl":lisp"ccl-bin")
(run-tests
:name"test-on-allegro":lisp"allegro")))

Here is how these jobs will look like in the GitHub interface:

Building Docs

Third predefined job type is 40ants-ci/jobs/docs:build-docs (12). It uses 40ants/build-docs action and will work only if your ASDF system uses a documentation builder supported by 40ants/docs-builder.

To build docs on every push to master, just use this code:

(defworkflow docs
:on-push-to"master":jobs ((40ants-ci/jobs/docs:build-docs)))

It will generate .github/workflows/docs.yml with following content:

{
"name": "DOCS",
"on": {
"push": {
"branches": [
"master"
]
}
},
"jobs": {
"build-docs": {
"runs-on": "ubuntu-latest",
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
},
"steps": [
{
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "example",
"qlfile-template": ""
}
},
{
"name": "Build Docs",
"uses": "40ants/build-docs@v1",
"with": {
"asdf-system": "example"
}
}
]
}
}
}

Caching

To significantly speed up our tests, we can cache installed Roswell, Qlot and Common Lisp fasl files.

To accomplish this task, you don't need to dig into GitHub's docs anymore! Just add one line :cache t to your workflow definition:

(defworkflow docs
:on-push-to"master":cachet:jobs ((40ants-ci/jobs/docs:build-docs)))

Here is the diff of the generated workflow file. It shows steps, added automatically:

modified .github/workflows/docs.yml
@@ -20,13 +20,40 @@
"name": "Checkout Code",
"uses": "actions/checkout@v4"
},
+ {+ "name": "Grant All Perms to Make Cache Restoring Possible",+ "run": "sudo mkdir -p /usr/local/etc/roswell\n sudo chown \"${USER}\" /usr/local/etc/roswell\n # Here the ros binary will be restored:\n sudo chown \"${USER}\" /usr/local/bin",+ "shell": "bash"+ },+ {+ "name": "Get Current Month",+ "id": "current-month",+ "run": "echo \"::set-output name=value::$(date -u \"+%Y-%m\")\"",+ "shell": "bash"+ },+ {+ "name": "Cache Roswell Setup",+ "id": "cache",+ "uses": "actions/cache@v3",+ "with": {+ "path": "qlfile\n qlfile.lock\n /usr/local/bin/ros\n ~/.cache/common-lisp/\n ~/.roswell\n /usr/local/etc/roswell\n .qlot",+ "key": "${{ steps.current-month.outputs.value }}-${{ env.cache-name }}-ubuntu-latest-quicklisp-sbcl-bin-${{ hashFiles('qlfile.lock') }}"+ }+ },+ {+ "name": "Restore Path To Cached Files",+ "run": "echo $HOME/.roswell/bin >> $GITHUB_PATH\n echo .qlot/bin >> $GITHUB_PATH",+ "shell": "bash",+ "if": "steps.cache.outputs.cache-hit == 'true'"+ },
{
"name": "Setup Common Lisp Environment",
"uses": "40ants/setup-lisp@v4",
"with": {
"asdf-system": "40ants-ci",
"qlfile-template": ""
- }+ },+ "if": "steps.cache.outputs.cache-hit != 'true'"
},
{

Adding env variables

You can specify additional environment variables on any level of the GitHub workflow: for workflow itself, for a job or for a step.

To specify env for workflow or a job, just add an ENV argument with alist or plist value like this:

(defworkflow release
:on-push-to"master":env (:github-token"${{ secrets.autotag_token }}")
:jobs ((40ants-ci/jobs/autotag:autotag)))

or as alist:

(defworkflow release
:on-push-to"master":env (("github_token"."${{ secrets.autotag_token }}"))
:jobs ((40ants-ci/jobs/autotag:autotag)))

or for the job itself:

(defworkflow release
:on-push-to"master":jobs ((40ants-ci/jobs/autotag:autotag
:env (:github-token"${{ secrets.autotag_token }}"))))

the same way it can be specified on a custom step:

(40ants-ci/steps/sh:sh "Custom env-var example""echo $CUSTOM_VAR":env (:custom-var"Hello world!"))

Note - environment variable names are always transformed to uppercase and dashes are replaced with underscores.

Running custom steps

Sometimes you might need to install custom system packages or do something before the job will finish. To accomplish these task you can provide custom steps using BEFORE-STEPS argument or AFTER-STEPS argument.

Here is an example where we are installing system package libunaq1-dev before running the testsuite:

(defparameter*required-steps*
(list (sh "Install libunac""sudo apt-get install -y libunac1-dev")))
(defworkflow ci
:on-pull-requestt:cachet:jobs ((run-tests
:steps-before*required-steps*:asdf-system"my-asdf-system")))

Details

TODO: I have to write a few chapters with details on additional job's parameters and a way how to create new job types.

But for now, I want to show a small example, how to define a workflow with a job which takes care about lisp installation and then calls a custom step:

(defworkflow ci
:on-push-to"master":by-cron"0 10 * * 1":on-pull-requestt:cachet:jobs ((40ants-ci/jobs/lisp-job:lisp-job :name"check-ros-config":lisp"ccl-bin":steps ((40ants-ci/steps/sh:sh "Show Roswell Config""ros config")))))

Here we are using the class 40ants-ci/jobs/lisp-job:lisp-job which is base for most classes in this ASDF system and pass a custom 40ants-ci/steps/sh:sh (12) step to it. This step will be called after the repostory checkout and CCL-BIN lisp installation. so, thus when this step will run ros config command, it will output something like that:

asdf.version=3.3.5.3
ccl-bin.version=1.12.2
setup.time=3918000017
sbcl-bin.version=2.4.1
default.lisp=ccl-bin
Possible subcommands:
set
show

Pay attention to the NAME argument of 40ants-ci/jobs/lisp-job:lisp-job class. If you omit it, then default "lisp-job" name will be used.

API

40ANTS-CI

package40ants-ci

Functions

function40ants-ci:generate system &key path

Generates GitHub workflow for given ASDF system.

This function searches workflow definitions in all packages of the given ASDF system.

If PATH argument is not given, workflow files will be written to .github/workflow/ relarive to the SYSTEM.

40ANTS-CI/GITHUB

package40ants-ci/github

Generics

generic-function40ants-ci/github:generate obj path

generic-function40ants-ci/github:prepare-data obj

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

40ANTS-CI/JOBS/AUTOTAG

package40ants-ci/jobs/autotag

Classes

AUTOTAG

class40ants-ci/jobs/autotag:autotag (job)

This type of the job created a git tag when finds a new tag in specified file.

Readers

reader40ants-ci/jobs/autotag:filename (autotag) (:filename = *default-filename*)

File where to search for version numbers.

reader40ants-ci/jobs/autotag:regex (autotag) (:regex = *default-regex*)

Regexp used to extract version numbers.

reader40ants-ci/jobs/autotag:tag-prefix (autotag) (:tag-prefix = *default-tag-prefix*)

Tag prefix.

reader40ants-ci/jobs/autotag:token-pattern (autotag) (:token-pattern = *default-token-pattern*)

Auth token pattern.

Functions

function40ants-ci/jobs/autotag:autotag &key (filename *default-filename*) (regex *default-regex*) (tag-prefix *default-tag-prefix*) (token-pattern *default-token-pattern*) env

Creates a job which will run autotagger to create a new git tag for release.

40ANTS-CI/JOBS/CRITIC

package40ants-ci/jobs/critic

Classes

CRITIC

class40ants-ci/jobs/critic:critic (lisp-job)

Readers

reader40ants-ci/jobs/critic:asdf-systems (critic) (:asdf-systems)

Critic can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/critic:ignore-critiques (critic) (:ignore-critiques)

A list strigns with names of critiques to ignore.

Functions

function40ants-ci/jobs/critic:critic &key asdf-systems asdf-version ignore-critiques env

Creates a job which will run Lisp Critic for given ASDF systems.

If argument ASDF-SYSTEMS is NIL, it will use ASDF system to which current lisp file is belong.

You may also provide ASDF-VERSION argument. It should be a string. By default, the latest ASDF version will be used.

40ANTS-CI/JOBS/DOCS

package40ants-ci/jobs/docs

Classes

BUILD-DOCS

class40ants-ci/jobs/docs:build-docs (lisp-job)

Builds documentation and uploads it to GitHub using "40ants/build-docs" github action.

Readers

reader40ants-ci/jobs/docs:error-on-warnings (build-docs) (:error-on-warnings = t)

Functions

function40ants-ci/jobs/docs:build-docs &rest args &key (error-on-warnings t) os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp asdf-system qlfile checkout-submodules dynamic-space-size

Creates a job of class build-docs.

40ANTS-CI/JOBS/JOB

package40ants-ci/jobs/job

Classes

JOB

class40ants-ci/jobs/job:job ()

Readers

reader40ants-ci/jobs/job:exclude (job) (:exclude = nil)

A list of plists denoting matrix combinations to be excluded.

reader40ants-ci/jobs/job:explicit-steps (job) (:steps = nil)

This slot holds steps given as a STEPS argument to a job constructor. Depending on a job class, it might add additional steps around these explicit steps.

reader40ants-ci/jobs/job:job-env (job) (:env = nil)

An alist of environment variables and their values to be added on job level. Values are evaluated in runtime.

reader40ants-ci/jobs/job:name (job) (:name)

If this name was not given in constructor, then name will be lowercased name of the job class.

reader40ants-ci/jobs/job:os (job) (:OS = "ubuntu-latest")

reader40ants-ci/jobs/job:permissions (job) (:permissions = nil)

A plist of permissions need for running the job.

These permissions will be bound to secrets.GITHUB_TOKEN variable. Use default-initargs to override permissions in subclasses:

(:default-initargs:permissions'(:content"write"))

reader40ants-ci/jobs/job:steps-after (job) (:steps-after = nil)

This slot holds steps given as a STEPS-AFTER argument to a job constructor. These steps will be appended to steps returned by the job class.

reader40ants-ci/jobs/job:steps-before (job) (:steps-before = nil)

This slot holds steps given as a STEPS-BEFORE argument to a job constructor. These steps will be prepended to steps returned by the job class.

Generics

generic-function40ants-ci/jobs/job:make-env job

generic-function40ants-ci/jobs/job:make-matrix job

generic-function40ants-ci/jobs/job:make-permissions job

Should return an alist with mapping from string to string where keys are scopes and values are permission names. Default method generates this alist from the plist of job's "permissions" slot.

generic-function40ants-ci/jobs/job:steps job

generic-function40ants-ci/jobs/job:use-matrix-p job

40ANTS-CI/JOBS/LINTER

package40ants-ci/jobs/linter

Classes

LINTER

class40ants-ci/jobs/linter:linter (lisp-job)

Readers

reader40ants-ci/jobs/linter:asdf-systems (linter) (:asdf-systems = nil)

Linter can validate more than one system, but for the base class we need provide only one.

reader40ants-ci/jobs/linter:check-imports (linter) (:check-imports = nil)

Linter will check for missing or unused imports of package-inferred systems.

Functions

function40ants-ci/jobs/linter:linter &rest args &key asdf-systems check-imports os permissions exclude env steps steps-before steps-after roswell-version asdf-version qlot-version quicklisp lisp qlfile checkout-submodules dynamic-space-size

Creates a job which will run SBLint for given ASDF systems.

If no ASD files given, it will use all ASD files from the current ASDF system.

40ANTS-CI/JOBS/LISP-JOB

package40ants-ci/jobs/lisp-job

Classes

LISP-JOB

class40ants-ci/jobs/lisp-job:lisp-job (job)

This job checkouts the sources, installs Roswell and Qlot. Also, it caches results between runs.

Readers

reader40ants-ci/jobs/lisp-job:asdf-system (lisp-job) (:asdf-system = nil)

reader40ants-ci/jobs/lisp-job:asdf-version (lisp-job) (:asdf-version = nil)

ASDF version to use when setting up Lisp environment. If NIL, then the latest will be used.

reader40ants-ci/jobs/lisp-job:checkout-submodules (lisp-job) (:checkout-submodules = nil)

If this flag is true, then we will command actions/checkout action to checkout submodules.

reader40ants-ci/jobs/lisp-job:dynamic-space-size (lisp-job) (:dynamic-space-size = nil)

Dynamic space size for SBCL.

reader40ants-ci/jobs/lisp-job:lisp (lisp-job) (:LISP = "sbcl-bin")

reader40ants-ci/jobs/lisp-job:qlfile (lisp-job) (:qlfile = nil)

reader40ants-ci/jobs/lisp-job:qlot-version (lisp-job) (:qlot-version = nil)

Qlot version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

reader40ants-ci/jobs/lisp-job:quicklisp (lisp-job) (:QUICKLISP = "quicklisp")

reader40ants-ci/jobs/lisp-job:roswell-version (lisp-job) (:roswell-version = nil)

Roswell version to use when setting up Lisp environment. If NIL, then will be used version, pinned in setup-lisp github action.

40ANTS-CI/JOBS/RUN-TESTS

package40ants-ci/jobs/run-tests

Classes

RUN-TESTS

class40ants-ci/jobs/run-tests:run-tests (lisp-job)

This job test runs tests for a given ASDF system.

Readers

reader40ants-ci/jobs/run-tests:coverage (run-tests) (:coverage = nil)

reader40ants-ci/jobs/run-tests:custom (run-tests) (:custom = nil)

Functions

function40ants-ci/jobs/run-tests:run-tests &rest rest &key coverage custom os permissions steps steps-before steps-after env roswell-version asdf-version qlot-version lisp exclude qlfile quicklisp asdf-system checkout-submodules dynamic-space-size

Creates a job step of class run-tests.

40ANTS-CI/STEPS/ACTION

package40ants-ci/steps/action

Classes

ACTION

class40ants-ci/steps/action:action (step)

Readers

reader40ants-ci/steps/action:action-args (action) (:args)

A plist to be passed as "with" dictionary to the action.

reader40ants-ci/steps/action:uses (action) (:uses)

Functions

function40ants-ci/steps/action:action name uses &rest args &key id if env &allow-other-keys

40ANTS-CI/STEPS/SH

package40ants-ci/steps/sh

Classes

SH

class40ants-ci/steps/sh:sh (step)

Readers

reader40ants-ci/steps/sh:command (sh) (:command)

reader40ants-ci/steps/sh:shell (sh) (:shell = *default-shell*)

Functions

function40ants-ci/steps/sh:sh name command &key id if (shell *default-shell*) env

Macros

macro40ants-ci/steps/sh:sections &body body

Returns a string with a bash script where some parts are grouped.

In this example we have 3 sections:

(sections
("Help Argument""qlot exec cl-info --help")
("Version Argument""qlot exec cl-info --version")
("Lisp Systems Info""qlot exec cl-info""qlot exec cl-info cl-info defmain"))

It will be compiled into:

echo ::group::Help Argument
qlot exec cl-info --help
echo ::endgroup::
echo ::group::Version Argument
qlot exec cl-info --version
echo ::endgroup::
echo ::group::Lisp Systems Info
qlot exec cl-info
qlot exec cl-info cl-info defmain
echo ::endgroup::

40ANTS-CI/STEPS/STEP

package40ants-ci/steps/step

Classes

STEP

class40ants-ci/steps/step:step ()

Readers

reader40ants-ci/steps/step:env (step) (:env = nil)

An alist of environment variables.

reader40ants-ci/steps/step:step-id (step) (:id = nil)

reader40ants-ci/steps/step:step-if (step) (:if = nil)

reader40ants-ci/steps/step:step-name (step) (:name = nil)

40ANTS-CI/UTILS

package40ants-ci/utils

Generics

generic-function40ants-ci/utils:system-packages system

Returns a list of packages created by ASDF system.

Default implementation returns a package having the same name as a system and all packages matched to package-inferred subsystems:

CL-USER> (docs-builder/utils:system-packages :docs-builder)
(#<PACKAGE "DOCS-BUILDER">
#<PACKAGE "DOCS-BUILDER/UTILS">
#<PACKAGE "DOCS-BUILDER/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/GENEVA/GUESSER">
#<PACKAGE "DOCS-BUILDER/BUILDER">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/GUESSER">
#<PACKAGE "DOCS-BUILDER/DOCS">
#<PACKAGE "DOCS-BUILDER/BUILDERS/MGL-PAX/BUILDER">)

Functions

function40ants-ci/utils:alistp list

Test wheather LIST argument is a properly formed alist.

In this library, alist has always a string as a key. Because we need them to have this form to serialize to JSON propertly.

(alistp '(("cron" . "0 10 * * 1"))) -> T (alistp '((("cron" . "0 10 * * 1")))) -> NIL

function40ants-ci/utils:current-system-name

function40ants-ci/utils:dedent text

Removes common leading whitespace from each string.

A few examples:

(dedent "Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "
Hello
World
and all Lispers!")
"Hello
World
and all Lispers!"
(dedent "This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD.")
"This is a code:
(symbol-name :hello-world)
it will output HELLO-WORLD."

function40ants-ci/utils:ensure-list-of-plists data

function40ants-ci/utils:ensure-primary-system system

function40ants-ci/utils:make-github-workflows-path system

function40ants-ci/utils:plist-to-alist plist &key (string-keys t) (lowercase t)

Make an alist from a plist PLIST.

By default, transforms keys to lowercased strings

function40ants-ci/utils:plistp list

Test wheather LIST is a properly formed plist.

function40ants-ci/utils:single list

Test wheather LIST contains exactly 1 element.

function40ants-ci/utils:to-json data

40ANTS-CI/VARS

package40ants-ci/vars

Variables

variable40ants-ci/vars:*current-system* -unbound-

When workflow is generated for ASDF system, this variable will contain a primary ASDF system.

variable40ants-ci/vars:*use-cache* nil

Workflow will set this variable when preparing the data or YAML generation.

40ANTS-CI/WORKFLOW

package40ants-ci/workflow

Classes

WORKFLOW

class40ants-ci/workflow:workflow ()

Readers

reader40ants-ci/workflow:by-cron (workflow) (:by-cron = "0 10 * * 1")

reader40ants-ci/workflow:cache-p (workflow) (:cache = t)

reader40ants-ci/workflow:jobs (workflow) (:jobs = nil)

reader40ants-ci/workflow:name (workflow) (:name)

reader40ants-ci/workflow:on-pull-request (workflow) (:on-pull-request = t)

reader40ants-ci/workflow:on-push-to (workflow) (:ON-PUSH-TO = "master")

reader40ants-ci/workflow:workflow-env (workflow) (:env = nil)

An alist of environment variables and their values to be added on workflow level. Values are evaluated in runtime.

Macros

macro40ants-ci/workflow:defworkflow name &key on-push-to by-cron on-pull-request cache env jobs

[generated by 40ANTS-DOC]

About

Highly opionated Github Actions workflow builder for Common Lisp projects.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages