Home

Awesome

yao: a Common Lisp task runner

ABOUT

yao enables Common Lisp developers to configure build tasks directly with Common Lisp code. This is a powerful advantage over the classic make utility. No longer do developers have to manage two completely different programming languages. With yao, Common Lisp devs have more expressiveness over their builds.

EXAMPLE

$ cd example

$ ./yao
Hello World!

$ ./yao test
Hello World!

SETUP

yao demonstrates a basic task runner in Common Lisp. This example specifically targets CLISP, though you may write similar task runners for other Common Lisp implementations.

Inside the example directory, we have a simple hello.lisp application representing a Common Lisp project. At the same level of the project is a yao task runner script.

Create a yao Common Lisp script file at the top level of your project. Omit any file extensions. Ensure the file receives executable chmod bits.

Shebang

#!/bin/sh
#|
exec clisp -q -q $0 $0 ${1+"$@"}
|#

The complex shebang above provides a POSIX compliant way to load the Common Lisp interpreter and query CLI arguments.

The repetition $0 $0 in the shebang works around a limitation in CLISP, which historically failed to present the name of the script for querying.

Import libraries

; https://www.quicklisp.org/beta/
(load "~/quicklisp/setup.lisp")
(ql:quickload 'uiop :silent t)

This example uses a portable, but not HyperSpec standardized, function run-program from the uiop third party package. uiop is available from the Quicklisp registry.

Create a task

(defun test ()
  (uiop:run-program '("clisp" "hello.lisp") :output t))

yao declares tasks in the form of simple Common Lisp functions.

Note the shell module run function, which takes a list of command and argument literals, and executes them as a subprocess.

This example merely executes the hello.lisp script. You will likely want to alter your project's test task to trigger a unit test suite.

A more typical Common Lisp project may feature lint tasks to detect coding quirks, clean tasks to remove build artifacts, and/or a build task to trigger compilation commands.

Entrypoint

(let ((default-task "test")
      (args (cdr *args*)))
  (if (eq (list-length args) 0)
      (funcall (intern (string-upcase default-task)))
      (mapcar #'(lambda (arg)
                  (funcall (intern (string-upcase arg))))
              args)))

Above, we have a Scheme entrypoint. The entrypoint declares the symbol 'test as the default task, simililar to the all default task convention in the make build system. You may choose another task symbol, whichever task is most relevant for your project.

When the user executes the shell command ./yao with no arguments, then the default task processes.

When the user supplies some task arguments like ./yao test, then the default task is ignored, and only the list of named tasks in the command line arguments will process.

Further Research

Use a Common Lisp parser to extract the function names from its own script, then generate a usage message as a help task.

Validate command line arguments against the list of task names.

Clean up some DRY violations, by having the args variable default like (list <default task>) when no CLI arguments are specified. (The DRY violation also deserves to be cleaned up in the related mian Scheme example.)

SEE ALSO

😈