The Common Lisp Cookbook – Defining Systems

Table of Contents

The Common Lisp Cookbook – Defining Systems

📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo

📕 Get the EPUB and PDF

A system is a collection of Lisp files that together constitute an application or a library, and that should therefore be managed as a whole. A system definition describes which source files make up the system, what the dependencies among them are, and the order they should be compiled and loaded in.

ASDF

ASDF is the standard build system for Common Lisp. It is shipped in most Common Lisp implementations. It includes UIOP, “the Utilities for Implementation- and OS- Portability”. You can read its manual and the tutorial and best practices.

Simple examples

Loading a system definition

When you start your Lisp, it knows about its internal modules and, by default, it has no way to know that your shiny new project is located under your ~/code/foo/bar/new-ideas/ directory. So, in order to load your project in your image, you have one of three ways:

Please read our section on the getting started#how-to-load-an-existing-project page.

Loading a system

Once your Lisp knows what your system is and where it lives, you can load it.

The most trivial use of ASDF is by calling asdf:load-system to load your library. Then you can use it. For instance, if it exports a function some-fun in its package foobar, then you will be able to call it with (foobar:some-fun ...) or with:

(in-package :foobar)
(some-fun ...)

You can also use Quicklisp.

Quicklisp calls ASDF under the hood, with the advantage that it will download and install any dependency if they are not already installed.

(ql:quickload "foobar")
;; =>
;; installs all dependencies
;; and loads the system.

Also, you can use SLIME to load a system, using the M-x slime-load-system Emacs command or the , load-system comma command in the prompt. The interesting thing about this way of doing it is that SLIME collects all the system warnings and errors in the process, and puts them in the *slime-compilation* buffer, from which you can interactively inspect them after the loading finishes.

Testing a system

To run the tests for a system, you may use:

(asdf:test-system :foobar)

The convention is that an error SHOULD be signalled if tests are unsuccessful.

Designating a system

The proper way to designate a system in a program is with lower-case strings, not symbols, as in:

(asdf:load-system "foobar")
(asdf:test-system "foobar")

How to write a trivial system definition

A trivial system would have a single Lisp file called foobar.lisp, located at the project’s root. That file would depend on some existing libraries, say alexandria for general purpose utilities, and trivia for pattern-matching. To make this system buildable using ASDF, you create a system definition file called foobar.asd, with the following contents:

(asdf:defsystem "foobar"
  :depends-on ("alexandria" "trivia")
  :components ((:file "foobar")))

Note how the type lisp of foobar.lisp is implicit in the name of the file above. As for contents of that file, they would look like this:

(defpackage :foobar
  (:use :common-lisp :alexandria :trivia)
  (:export
   #:some-function
   #:another-function
   #:call-with-foobar
   #:with-foobar))

(in-package :foobar)

(defun some-function (...)
    ...)
...

Instead of using multiple complete packages, you might want to just import parts of them:

(defpackage :foobar
  (:use #:common-lisp)
  (:import-from #:alexandria
                #:some-function
                #:another-function))
  (:import-from #:trivia
                #:some-function
                #:another-function))
...)

Using the system you defined

Assuming your system is installed under ~/common-lisp/, ~/quicklisp/local-projects/ or some other filesystem hierarchy already configured for ASDF, you can load it with: (asdf:load-system "foobar").

If your Lisp was already started when you created that file, you may have to, either:

How to write a trivial testing definition

Even the most trivial of systems needs some tests, if only because it will have to be modified eventually, and you want to make sure those modifications don’t break client code. Tests are also a good way to document expected behavior.

The simplest way to write tests is to have a file foobar-tests.lisp and modify the above foobar.asd as follows:

(asdf:defsystem "foobar"
    :depends-on ("alexandria" "trivia")
    :components ((:file "foobar"))
    :in-order-to ((test-op (test-op "foobar/tests"))))

(asdf:defsystem "foobar/tests"
    :depends-on ("foobar" "fiveam")
    :components ((:file "foobar-tests"))
    :perform (test-op (o c) (symbol-call :fiveam '#:run! :foobar)))

The :in-order-to clause in the first system allows you to use (asdf:test-system :foobar) which will chain into foobar/tests. The :perform clause in the second system does the testing itself.

In the test system, fiveam is the name of a popular test library, and the content of the perform method is how to invoke this library to run the test suite :foobar. Obvious YMMV if you use a different library.

Create a project skeleton

cl-project can be used to generate a project skeleton. It will create a default ASDF definition, generate a system for unit testing, etc.

Install with

(ql:quickload "cl-project")

Create a project:

(cl-project:make-project #p"lib/cl-sample/"
:author "Eitaro Fukamachi"
:email "e.arrows@gmail.com"
:license "LLGPL"
:depends-on '(:clack :cl-annot))
;-> writing /Users/fukamachi/Programs/lib/cl-sample/.gitignore
;   writing /Users/fukamachi/Programs/lib/cl-sample/README.markdown
;   writing /Users/fukamachi/Programs/lib/cl-sample/cl-sample-test.asd
;   writing /Users/fukamachi/Programs/lib/cl-sample/cl-sample.asd
;   writing /Users/fukamachi/Programs/lib/cl-sample/src/hogehoge.lisp
;   writing /Users/fukamachi/Programs/lib/cl-sample/t/hogehoge.lisp
;=> T

And you’re done.

Page source: systems.md

T
O
C