When you decide to make an application standalone using the two step process it is possible that you may encounter the following situation: the build process completes without a problem but running the program lands you in the debugger with a message about Quicklisp not being found.

Why is Quicklisp a requirement to run a standalone application?

Some libraries can use a variety of sub-libraries, of which they only need one, to provide their service. The libraries are optimized to load only the dependency which is actually used and none of the others. An example of such a case is cl-dbi which can use MySQL, PostgreSQL and SQLite as backends but only loads the driver for the active database.

One method to implement this dependency optimization is to load the sub-library at runtime using Quicklisp. This forces you to include Quicklisp in your binary which defeats the whole point of the two step build process.

To date I have used three libraries that uses Quicklisp at runtime: crane, lack (via Clack) and cl-dbi. All three use #+/#-quicklisp to select between Quicklisp and ASDF as systems loader. In principle this should allow the creation of binaries which don’t contain Quicklisp but it doesn’t work because the libraries are only ever compiled when Quicklisp is available.

The solution1 for using these libraries without Quicklisp in the binary is to provide the referenced Quicklisp function as an empty stub. The stub must only be included when building the binary.

To implement the solution, add a file ql-patch.lisp to your project

;;file name: ql-patch.lisp

(defpackage quicklisp-client
  (:use :cl)
  (:nicknames :ql)
  (:export :quickload)
  (:documentation "Quicklisp look-alike fake package."))
(in-package :quicklisp-client)

(defun quickload (&rest args)
  "This is a load-bearing hack."
  t)

and update the ‘Buildapp settings’ section in your makefile2 to load ql-patch.lisp before loading your system.

# Buildapp settings
B_FLAGS =  --output $(OUTDIR)/$(TARGET)
B_FLAGS += --manifest-file $(MANIFEST)
B_FLAGS += --load ql-patch.lisp        # <<< Insert this line
B_FLAGS += --load-system $(QL_SYSTEM)
B_FLAGS += --entry app:main

The patch does nothing, how does it work?

Each of the three libraries mentioned before contain code of the form

#+quicklisp
(ql:quickload ...)
#-quicklisp
(asdf:load-system ...)

With the two step build process everything is compiled during the manifest creation stage when Quicklisp is still available. This causes the (ql:quickload) call to be included in the compiled code.

During the binary creation stage Quicklisp is not present and the previously compiled code is loaded using ASDF. The (ql:quickload) call does not yet cause an error because it is used inside a function and will only be executed at runtime.

When the time comes to load the dependency the (ql:quickload) call is executed irrespective of whether the target system is already present or not.

The stub function’s purpose is purely to prevent an ‘undefined function’ error. It should not do anything else because the required library should be loaded already.

This scheme requires that all the libraries that the (ql:quickload) call intends to load be present already. The easiest method to accomplish this is to include them in the application’s system definition in the depends-on section.


Footnotes:

  1. I found this solution in @eudxa’s project Ceramic. Original ql-patch file.

  2. See the example makefile in the post about the two step process.