A Modern Guide to Common Lisp Package Management
Quicklisp, Roswell, and Ultralisp—when to use each, and how to avoid the common setup traps
You decided to try Common Lisp.
You did the sensible thing: you found a library you want to use (cl-excel, or any other). You expect a short story:
“Install thing. Load thing. Use thing.”
And then you open a README.md and it says something like:
- “Put it in
local-projects… unless you don’t havelocal-projects… unless you useRoswell… unless your Lisp loadsQuicklispautomatically… unless it doesn’t…” - “Edit
.sbclrc… or~/.roswell/init.lisp… or maybe symlink…” - “Run this tiny incantation.”
So your brain starts doing the classic beginner move: doubting itself.
Let’s delete the drama.
By the end of this article you’ll know:
- what Quicklisp is (and why it’s everywhere),
- what Roswell does (and why people recommend it),
- what Ultralisp is (and why it feels like “Quicklisp but faster”),
- and the one install workflow that keeps you sane.
We’ll use cl-excel as the concrete example, but this applies to most Common Lisp libraries.
The cast of characters
Quicklisp — the package manager you’ll meet everywhere
Quicklisp is the de-facto library distribution system in Common Lisp land. When someone says “install a Lisp library”, they usually mean “Quicklisp can find it and load it.”
Quicklisp’s killer feature is also the reason beginners get confused:
- It can load libraries from its curated “dist” (the default list of libraries).
- It can also load libraries from your own machine, if you place them in a known folder (more on that soon).
So Quicklisp is both:
- the thing that downloads libraries
- and the thing that searches your local filesystem for libraries
That’s convenient… once you understand where it looks.
Roswell — the tool that makes your Lisp feel like a real toolchain
Roswell is a Lisp environment manager. It can install Lisp implementations (SBCL, CCL, etc.), manage scripts, and it comes with a Quicklisp integration that many people use by default.
Think of Roswell like a practical “Lisp toolbox” that makes your setup reproducible and less artisanal.
And it matters for one reason:
Roswell has its own local-projects directory.
So when a README says “clone into local-projects”, you need to know whether they mean:
- Quicklisp’s local-projects, or
- Roswell’s local-projects
We’ll resolve that cleanly in a moment.
Ultralisp — Quicklisp’s faster cousin
Ultralisp is another Quicklisp “dist” you can add. The important idea:
- Quicklisp updates on a slower schedule.
- Ultralisp updates very frequently.
So if you maintain a library, or you want newer versions quickly, Ultralisp is often the path of least pain.
For a beginner, Ultralisp is great because the install experience is the same as Quicklisp once it’s enabled:
(ql:quickload :some-library)That’s the dream.
The single mental model that fixes 80% of install pain
Here it is:
Quicklisp loads a system if it can find it.
It can find it in two major places:
- In a dist (Quicklisp’s default dist, or Ultralisp, etc.)
- In your local-projects directories (folders on your machine you tell Quicklisp to scan)
So every installation method is just one of these:
- “Make it available via a dist”
- “Put it where Quicklisp scans locally”
That’s it.
The rest is workflow.
The friend-guided path: choose one of three workflows
We’ll do this like a competent friend would: pick the simplest option first.
Workflow A: “One command” (Roswell)
This is the beginner-friendly route when the project supports it.
You run:
ros install gwangjinkim/cl-excelWhat happens conceptually:
- Roswell downloads your project and puts it into a place it manages.
- Roswell’s Quicklisp integration sees it.
- You can load it like any other Quicklisp system.
Now open a REPL (for example with ros run or however you start your Lisp) and do:
(ql:quickload :cl-excel)Why this feels so good:
- no guessing where to clone
- no touching init files
- the install command is the documentation
If your audience uses Roswell, this is the best “zero mental overhead” path.
Workflow B: “Fast dist” (Ultralisp)
This is the path you’ll love as a library maintainer and as a user who likes fresh versions.
First: enable Ultralisp once (global setup):
(ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)What just happened:
- Quicklisp keeps a registry of “dists”.
- You just told it: “also consult Ultralisp’s dist.”
Now, if cl-excel is in Ultralisp, your install becomes:
(ql:quickload :cl-excel)That’s it.
Why this is powerful:
- you get new versions quickly
- users install without cloning repos manually
- your README becomes shorter
Ultralisp is the “stop making users do archaeology” option.
Workflow C: “Local clone” (Quicklisp now, dist later)
This is the practical workflow when your library is not yet in Quicklisp (or you don’t want to wait for dist turnover).
Step 1: clone into the right local-projects folder
If you use plain Quicklisp:
git clone git@github.com:gwangjinkim/cl-excel.git ~/quicklisp/local-projects/cl-excelIf you use Quicklisp via Roswell (if you start your Lisp sesssion with ros run):
git clone git@github.com:gwangjinkim/cl-excel.git ~/.roswell/local-projects/cl-excelWhat’s going on:
- Quicklisp scans local-projects for ASDF systems.
- By cloning into that folder, you made your project “discoverable”.
- Now Quicklisp can load it.
Step 2: load it
(ql:quickload :cl-excel)This is the most common “developer mode” workflow in Lisp:
- clone repo
- quickload it
No registry approvals required.
“But my repos are in ~/dev/, not in local-projects/”
Good. That means you’re organized.
Now choose how you want Quicklisp to discover your projects.
Option 1: Symlink (recommended)
Symlinks are the “do the simple thing that keeps working” approach.
If your repo lives at ~/dev/cl-excel:
For Roswell users:
ln -s ~/dev/cl-excel ~/.roswell/local-projects/cl-excelFor plain Quicklisp users:
ln -s ~/dev/cl-excel ~/quicklisp/local-projects/cl-excelWhy symlink wins:
- no Lisp init file edits
- no extra moving parts
- you can keep your folder structure and still satisfy Quicklisp
This is the “I want clean architecture in my home directory” option.
Option 2: Add your dev folder to Quicklisp’s search path (advanced)
Quicklisp has a variable:
ql:*local-project-directories*
It’s a list of directories Quicklisp scans for projects.
So you can permanently add ~/dev/ to that list.
If you use Roswell: ~/.roswell/init.lisp
(handler-case
(progn
(require :asdf)
(when (find-package :ql)
(pushnew #p"~/dev/" ql:*local-project-directories* :test #'equal)
(ql:register-local-projects)))
(error () nil))If you use SBCL directly: ~/.sbclrc
(load "~/quicklisp/setup.lisp")
(handler-case
(progn
(when (find-package :ql)
(pushnew #p"~/dev/" ql:*local-project-directories* :test #'equal)
(ql:register-local-projects)))
(error () nil))Let’s translate this into human:
handler-case ... (error () nil)Means: “If anything goes wrong here, don’t crash my startup.”(require :asdf)Ensures ASDF (the system loader) is available. Quicklisp relies on ASDF.(when (find-package :ql) ...)Only do the Quicklisp part if Quicklisp is actually loaded.(pushnew #p"~/dev/" ql:*local-project-directories* ...)Add your folder to the list, but only if it isn’t already there.(ql:register-local-projects)Tell Quicklisp: “Rescan local projects now.”
This approach is good when:
- you have many local libraries,
- you don’t want a forest of symlinks,
- you want Quicklisp to just “know” where your projects live.
But if you’re new: symlink first. You can always grow into this.
Why this matters: the “you just unlocked Lisp” moment
In many ecosystems, “package manager” means “download from the internet or nothing.”
Quicklisp is different: it’s perfectly happy to load libraries straight from your filesystem.
That’s not an accident. It matches how Lisp developers work:
- clone a library
- hack it locally
- load it immediately
- iterate fast
Once your brain accepts that “local projects are first-class,” Lisp development becomes very fluid.
It’s one of the underrated joys of the ecosystem.
A sane README structure you can reuse
If you maintain a library, here’s the clean structure that saves your readers:
- Roswell one-liner (if supported)
- Ultralisp (fast dist, best UX once available)
- Local clone into local-projects (works now, no waiting)
- “Keep repos elsewhere?” → recommend symlink, and optionally the init-file method
That’s it. No branching maze inside the first paragraph.
The practical cheat sheet
- Want the easiest install? Use Roswell if possible.
- Want fast distribution updates? Add Ultralisp.
- Want to use a library today before it’s in a dist? Clone into local-projects.
- Want your repos in ~/dev but still loadable? Symlink (first) or update init files (later).
And remember the only rule you really needed:
Quicklisp can only load what it can find.
So your job is simply to make the project discoverable.
Everything else is decoration.