Using a program from a REPL is fine and well, but if we want to
distribute our program easily, we’ll want to build an executable.

Lisp implementations differ in their processes, but they all create
self-contained executables, for the architecture they are built on. The
final user doesn’t need to install a Lisp implementation, he can run
the software right away.

Start-up times are near to zero, specially with SBCL and CCL.

Binaries size are large-ish. They include the whole Lisp
including its libraries, the names of all symbols, information about
argument lists to functions, the compiler, the debugger, source code
location information, and more.

Note that we can similarly build self-contained executables for web apps.

sb-ext is an SBCL extension to run external processes. See other
SBCL extensions
(many of them are made implementation-portable in other libraries).

:executable t tells to build an executable instead of an
image. We could build an image to save the state of our current
Lisp image, to come back working with it later. Specially useful if
we made a lot of work that is computing intensive.

If you try to run this in Slime, you’ll get an error about threads running:

With ASDF [updated]

Now that we’seen the basics, we need a portable method. Since its
version 3.1, ASDF allows to do that. It introduces the make command,
that reads parameters from the .asd. Add this to your .asd declaration:

We used the bordeaux-threads library ((ql:quickload
"bordeaux-threads"), alias bt) and uiop, which is part of ASDF so
already loaded, in order to exit in a portable way (uiop:quit, with
an optional return code, instead of sb-ext:quit).

Size and startup times of executables per implementation

SBCL isn’t the only Lisp implementation.
ECL, Embeddable
Common Lisp, transpiles Lisp programs to C. That creates a smaller
executable.

According to
this reddit source, ECL produces indeed the smallest executables of all,
an order of magnituted smaller than SBCL, but with a longer startup time.

Parsing

We parse and get the arguments with opts:get-opts, which returns two
values: the list of valid options and the remaining free arguments. We
then must use multiple-value-bind to assign both into variables:

Catching a C-c termination signal

Let’s build a simple binary, run it, try a C-c and read the stacktrace:

$ ./my-app
sleep…
^C
debugger invoked on a SB-SYS:INTERACTIVE-INTERRUPT in thread <== condition name
#<THREAD "main thread" RUNNING {1003156A03}>:
Interactive interrupt at #x7FFFF6C6C170.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [CONTINUE ] Return from SB-UNIX:SIGINT. <== it was a SIGINT indeed
1: [RETRY-REQUEST] Retry the same request.

The signaled condition is named after our implementation:
sb-sys:interactive-interrupt. We just have to surround our
application code with a handler-case:

here #+ includes the line at compile time depending on
the implementation. There’s also #-. What #+ does is to look for
symbols in the *features* list. We can also combine symbols with
and, or and not.

Continuous delivery of executables

We can make a Continuous Integration system (Travis CI, Gitlab CI,…)
build binaries for us at every commit, or at every tag pushed or at
wichever other policy.