A Modern Guide to Common Lisp Package Management

A Modern Guide to Common Lisp Package Management
Photo by Mohammad Rahmani / Unsplash

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 have local-projects… unless you use Roswell… unless your Lisp loads Quicklisp automatically… 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:

  1. In a dist (Quicklisp’s default dist, or Ultralisp, etc.)
  2. 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-excel

What 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-excel

If 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-excel

What’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.

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-excel

For plain Quicklisp users:

ln -s ~/dev/cl-excel ~/quicklisp/local-projects/cl-excel

Why 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:

  1. Roswell one-liner (if supported)
  2. Ultralisp (fast dist, best UX once available)
  3. Local clone into local-projects (works now, no waiting)
  4. “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.