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.
If you already have a system on your laptop, you can load it with, e.g.
(asdf:load-system :iterate)
If you do not yet have a system on your laptop, you can load it with, e.g.
(ql:quickload :iterate)
This will fetch the system, store it on your laptop and load it.
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.
- We should put the directory somewhere
quicklispandasdfcan find it. If you installedquicklispin the standard way then a good place is under~/quicklisp/local-projects/ - The pathname you give
cl-projectshould 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.asdis a system definition file. It contains definitions of two systems
foobarandfoobar/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,licenseanddescriptionfields. If we use any other packages we fill them in the:depends-onfield, e.g.:depends-on (:iterate :trivia)
README.markdown,README.org- Depending on whether we want to write our
READMEin markdown or Org Mode, we can delete one of these and edit the other. src,tests- There are two subdirectories. In
srcwe will put out definitions files. Intestswe 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