Packages and Systems

In Lisp a package is a collection of symbols, i.e. names of functions, variables, classes and so forth.

A package can export some or all of its symbols and a package can import symbols from another package.

A system is a collection of Lisp files that together constitute an application or a library. Often a system contains several packages.

The standard way of dealing with systems in Lisp is ASDF.

Quicklisp builds on ASDF by downloading systems as needed.

When we are writing a Lisp project of our own, it is helpful to adopt a standard layout. For this we use the cl-project system.

(ql:quickload :cl-project)

We use cl-project to make a project template in a directory of our choice. Two things are important here.

  1. We should put the directory somewhere quicklisp and asdf can find it. If you installed quicklisp in the standard way then a good place is under ~/quicklisp/local-projects/
  2. The pathname you give cl-project should end with a slash (/) so that is is recognised as a directory, and not a file.

Let’s make a test project called foobar

CL-USER> (cl-project:make-project #p"/home/student/quicklisp/local-projects/foobar/")
writing /home/student/quicklisp/local-projects/foobar/foobar.asd
writing /home/student/quicklisp/local-projects/foobar/README.org
writing /home/student/quicklisp/local-projects/foobar/README.markdown
writing /home/student/quicklisp/local-projects/foobar/.gitignore
writing /home/student/quicklisp/local-projects/foobar/src/main.lisp
writing /home/student/quicklisp/local-projects/foobar/tests/main.lisp
T

We see that six files are written. Let’s have a look at them.

.gitignore
contains some patterns of compiled Lisp files which we will not want to commit to git
foobar.asd

is a system definition file. It contains definitions of two systems foobar and foobar/tests

(defsystem "foobar"
  :version "0.0.1"
  :author ""
  :license ""
  :depends-on ()
  :components ((:module "src"
                :components
                ((:file "main"))))
  :description ""
  :in-order-to ((test-op (test-op "foobar/tests"))))

(defsystem "foobar/tests"
  :author ""
  :license ""
  :depends-on ("foobar"
               "rove")
  :components ((:module "tests"
                :components
                ((:file "main"))))
  :description "Test system for foobar"
  :perform (test-op (op c) (symbol-call :rove :run c)))

We can fill in the author, license and description fields. If we use any other packages we fill them in the :depends-on field, e.g.

:depends-on (:iterate :trivia)
README.markdown, README.org
Depending on whether we want to write our README in markdown or Org Mode, we can delete one of these and edit the other.
src, tests
There are two subdirectories. In src we will put out definitions files. In tests we will put our test files.
src/main.lisp
is an example definitions file
tests/main.lisp
is an example tests file containing one test.

Before we run the tests we should make sure that the rove testing library is on our system. In the Lisp interpreter:

* (ql:quickload :rove)

Now we can a run the tests for the foobar package. It runs the single test and reports success.

* (asdf:test-system :foobar)

Testing System foobar/tests

;; testing 'foobar/tests/main'
test-target-1
  should (= 1 1) to be true
    ✓ Expect (= 1 1) to be true.

✓ 1 test completed

Summary:
  All 1 test passed.
T

Now let’s add a new function and some tests for it.

Edit src/main.lisp as follows

(uiop:define-package foobar
  (:use #:cl)
  (:export :fact))

(in-package #:foobar)

(defun fact (n)
  (if (zerop n)
      1
      (* n (fact (- n 1)))))

Note how we have added a function called fact and exported it.

Now edit tests/main.lisp as follows

(defpackage foobar/tests/main
  (:use :cl
        :foobar
        :rove)
  (:import-from :foobar :fact))

(in-package :foobar/tests/main)

;; NOTE: To run this test file, execute `(asdf:test-system :foobar)' in your Lisp.

(deftest test-target-1
  (testing "should (= 1 1) to be true"
    (ok (= 1 1))))

(deftest test-fact
  (testing "should work as factorial function"
    (ok (= 1 (fact 1)))
    (ok (= 24 (fact 4)))))

Note how we added a new test containing two unit tests and we imported fact from foobar.

Now when we run our tests again, we see

* (asdf:test-system :foobar)
; compiling file "/home/student/quicklisp/local-projects/foobar/src/main.lisp" (written 16 APR 2026 09:29:02 AM):

; wrote /home/bon/.cache/common-lisp/sbcl-2.6.3.137.bon.5-718d051a5-linux-x64-s/home/student/quicklisp/local-projects/foobar/src/main-tmpGHU3ALSV.fasl
; compilation finished in 0:00:00.005
; compiling file "/home/student/quicklisp/local-projects/foobar/tests/main.lisp" (written 16 APR 2026 09:30:18 AM):

; wrote /home/bon/.cache/common-lisp/sbcl-2.6.3.137.bon.5-718d051a5-linux-x64-s/home/student/quicklisp/local-projects/foobar/tests/main-tmpAAURSO1.fasl
; compilation finished in 0:00:00.022

Testing System foobar/tests

;; testing 'foobar/tests/main'
test-target-1
  should (= 1 1) to be true
    ✓ Expect (= 1 1) to be true.
test-fact
  should work as factorial function
    ✓ Expect (= 1 (FACT 1)) to be true.
    ✓ Expect (= 24 (FACT 4)) to be true.

✓ 1 test completed

Summary:
  All 1 test passed.
T
*

ASDF first checks to see if any files in the system have changed and, if so, it recompiles them before running the tests.

Let’s add a deliberately wrong test (= 99 (fact 3)) to see that it fails.

* (asdf:test-system :foobar)
; compiling file "/home/student/quicklisp/local-projects/foobar/tests/main.lisp" (written 16 APR 2026 09:33:34 AM):

; wrote /home/bon/.cache/common-lisp/sbcl-2.6.3.137.bon.5-718d051a5-linux-x64-s/home/student/quicklisp/local-projects/foobar/tests/main-tmp5GEXGEG5.fasl
; compilation finished in 0:00:00.030

Testing System foobar/tests

;; testing 'foobar/tests/main'
test-target-1
  should (= 1 1) to be true
    ✓ Expect (= 1 1) to be true.
test-fact
  should work as factorial function
    ✓ Expect (= 1 (FACT 1)) to be true.
    ✓ Expect (= 24 (FACT 4)) to be true.
    × 0) Expect (= 99 (FACT 3)) to be true.

× 1 of 1 test failed

0) foobar/tests/main
     › TEST-FACT
       › should work as factorial function

   Expect (= 99 (FACT 3)) to be true.
       at /home/student/quicklisp/local-projects/foobar/tests/main.lisp:19:4
     (= 99 #1=(FOOBAR:FACT 3))
         #1# = 6

Summary:
  1 test failed.
    - foobar/tests/main
T
*

If we want to write some of our functions in a new file, we need to be sure to add it to the :components of the system definition in foobar.asd Here is an example.

:components ((:module "src"
              :components
              ((:file "main")
               (:file "other"))))

Now we edit the file src/other.lisp

(uiop:define-package foobar/other
  (:use #:cl)
  (:export :fact))

(in-package #:foobar/other)

(defun fib (n)
  (if (< n 2)
      1
      (+ (fib (- n 1)) (fib (- n 2)))))

If we want to add a test file for fib, we edit the :components of foobar/tests in foobar.asd and add a file in tests similar to tests/main.lisp

Author: Breanndán Ó Nualláin <o@uva.nl>

Date: 2026-04-20 Mon 16:34