<!-- <a style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPY-XMAS2023" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover our contributor's Lisp course with this Christmas coupon.</a> -->
📢 New videos: <ahref="https://www.youtube.com/watch?v=h_noB1sI_e8">web dev demo part 1</a>, <ahref="https://www.youtube.com/watch?v=xnwc7irnc8k">dynamic page with HTMX</a>, <ahref="https://www.youtube.com/watch?v=Zpn86AQRVN8">Weblocks demo</a>
(many of them are made implementation-portable in other libraries).</p>
<p><code>:executable t</code> 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. This is especially useful if
we made a lot of work that is computing intensive.
In that case, we re-use the image with <code>sbcl --core name-of-image</code>.</p>
<p><code>:toplevel</code> gives the program’s entry point, here <code>my-app:main-function</code>. Don’t forget to <code>export</code> the symbol, or use <code>my-app::main-function</code> (with two colons).</p>
<p>If you try to run this in Slime, you’ll get an error about threads running:</p>
<blockquote>
<p>Cannot save core with multiple threads running.</p>
</blockquote>
<p>We must run the command from a simple SBCL repl, from the terminal.</p>
<p>I suppose your project has Quicklisp dependencies. You must then:</p>
<ul>
<li>ensure Quicklisp is installed and loaded at the Lisp startup (you
completed Quicklisp installation),</li>
<li><code>asdf:load-asd</code> the project’s .asd (recommended instead of just <code>load</code>),</li>
<p>Now that we’ve seen the basics, we need a portable method. Since its
version 3.1, ASDF allows to do that. It introduces the <ahref="https://common-lisp.net/project/asdf/asdf.html#Convenience-Functions"><code>make</code> command</a>,
that reads parameters from the .asd. Add this to your .asd declaration:</p>
<pre><code>:build-operation "program-op" ;; leave as is
:build-pathname "<here your final binary name>"
Common Lisp, transpiles Lisp programs to C. That creates a smaller
executable.</p>
<p>According to
<ahref="https://www.reddit.com/r/lisp/comments/46k530/tackling_the_eternal_problem_of_lisp_image_size/">this reddit source</a>, ECL produces indeed the smallest executables of all,
an order of magnitude smaller than SBCL, but with a longer startup time.</p>
<p>CCL’s binaries seem to be as fast to start up as SBCL and nearly half the size.</p>
<pre><code>| program size | implementation | CPU | startup time |
<h3id="building-a-smaller-binary-with-sbcls-core-compression">Building a smaller binary with SBCL’s core compression</h3>
<p>Building with SBCL’s core compression can dramatically reduce your
application binary’s size. In our case, it reduced it from 120MB to 23MB,
for a loss of a dozen milliseconds of start-up time, which was still
under 50ms.</p>
<divclass="info-box info">
<strong>Note:</strong> SBCL 2.2.6 switched to compression with zstd instead of zlib, which provides smaller binaries and faster compression and decompression times. Un-official numbers are: about 4x faster compression, 2x faster decompression, and smaller binaries by 10%.
</div>
<p>Your SBCL must be built with core compression, see the documentation: <ahref="http://www.sbcl.org/manual/#Saving-a-Core-Image">Saving-a-Core-Image</a></p>
<p>Ultimately, we need to create a Clingon command (with
<code>clingon:make-command</code>) to represent our application. A command is
composed of options and of a handler function, to do the logic.</p>
<p>So first, let’s create options. Clingon already handles “–help” for us, but not the short version. Here’s how we use <code>clingon:make-option</code> to create an option:</p>
:flag ;; <--- option kind. A "flag" does not expect a parameter on the CLI.
:description "short help"
;; :long-name "help" ;; <--- long name, sans the "--" prefix, but here it's a duplicate.
:short-name #\h ;; <--- short name, a character
;; :required t ;; <--- is this option always required? In our case, no.
:key :help) ;; <--- the internal reference to use with getopt, see later.
</code></pre>
<p>This is a <strong>flag</strong>: if “-h” is present on the command-line, the
option’s value will be truthy, otherwise it will be falsy. A flag does
not expect an argument, it’s here for itself.</p>
<p>Similar kind of options would be:</p>
<ul>
<li><code>:boolean</code>: that one expects an argument, which can be “true” or 1 to be truthy. Anything else is considered falsy.</li>
<li><code>:counter</code>: a counter option counts how many times the option is provided on the command line. Typically, use it with <code>-v</code> / <code>--verbose</code>, so the user could use <code>-vvv</code> to have extra verbosity. In that case, the option value would be 3. When this option is not provided on the command line, Clingon sets its value to 0.</li>
</ul>
<p>We’ll create a second option (“–name” or “-n” with a parameter) and we put everything in a litle function.</p>
<pre><codeclass="language-lisp">;; The naming with a "/" is just our convention.
(defun cli/options ()
"Returns a list of options for our main command"
(list
(clingon:make-option
:flag
:description "short help."
:short-name #\h
:key :help)
(clingon:make-option
:string ;; <--- string type: expects one parameter on the CLI.
:description "Name to greet"
:short-name #\n
:long-name "name"
:env-vars '("USER") ;; <-- takes this default value if the env var exists.
:initial-value "lisper" ;; <-- default value if nothing else is set.
:key :name)))
</code></pre>
<p>The second option we created is of kind <code>:string</code>. This option expects one argument, which will be parsed as a string. There is also <code>:integer</code>, to parse the argument as an integer.</p>
<p>There are more option kinds of Clingon, which you will find on its good documentation: <code>:choice</code>, <code>:enum</code>, <code>:list</code>, <code>:filepath</code>, <code>:switch</code> and so on.</p>
<h3id="top-level-command">Top-level command</h3>
<p>We have to tell Clingon about our top-level command.
<code>clingon:make-command</code> accepts some descriptive fields, and two important ones:</p>
<ul>
<li><code>:options</code> is a list of Clingon options, each created with <code>clingon:make-option</code></li>
<li><code>:handler</code> is the function that will do the app’s logic.</li>
</ul>
<p>And finally, we’ll use <code>clingon:run</code> in our main function (the entry
point of our binary) to parse the command-line arguments, and apply
our command’s logic. During development, we can also manually call
<code>clingon:parse-command-line</code> to try things out.</p>
<p>Here’s a minimal command. We’ll define our handler function afterwards:</p>
<p>We can even <code>inspect</code> this command object, we would see its properties (name, hooks, description, context…), its list of options, etc.</p>
<p>We must tell our top-level command to use this handler:</p>
<pre><codeclass="language-lisp">;; from above:
(defun cli/command ()
"A command to say hello to someone"
(clingon:make-command
...
:handler #'cli/handler)) ;; <-- changed.
</code></pre>
<p>We now only have to write the main entry point of our binary and we’re done.</p>
<p>By the way, <code>clingon:getopt</code> returns 3 values:</p>
<ul>
<li>the option’s value</li>
<li>a boolean, indicating wether this option was provided on the command-line</li>
<li>the command which provided the option for this value.</li>
</ul>
<p>See also <code>clingon:opt-is-set-p</code>.</p>
<h3id="main-entry-point">Main entry point</h3>
<p>This can be any function, but to use Clingon, use its <code>run</code> function:</p>
<pre><codeclass="language-lisp">(defun main ()
"The main entrypoint of our CLI program"
(clingon:run (cli/command)))
</code></pre>
<p>To use this main function as your binary entry point, see above how to build a Common Lisp binary. A reminder: set it in your .asd system declaration:</p>
<p>And that’s about it. Congratulations, you can now properly parse command-line arguments!</p>
<p>Go check Clingon’s documentation, because there is much more to it: sub-commands, contexts, hooks, handling a C-c, developing new options such as an email kind, Bash and Zsh completion…</p>
<h2id="catching-a-c-c-termination-signal">Catching a C-c termination signal</h2>
<p>By default, <strong>Clingon provides a handler for SIGINT signals</strong>. It makes the
application to immediately exit with the status code 130.</p>
<p>If your application needs some clean-up logic, you can use an <code>unwind-protect</code> form. However, it might not be appropriate for all cases, so Clingon advertises to use the <ahref="https://github.com/compufox/with-user-abort">with-user-abort</a> helper library.</p>
<p>Below we show how to catch a C-c manually. Because by default, you would get a Lisp stacktrace.</p>
<p>We built a simple binary, we ran it and pressed <code>C-c</code>. Let’s read the stacktrace:</p>
<pre><code>$ ./my-app
sleep…
^C
debugger invoked on a SB-SYS:INTERACTIVE-INTERRUPT in thread <== condition name
<li><ahref="https://github.com/sionescu/sbcl-goodies">SBCL-GOODIES</a> - Allows to distribute SBCL binaries with foreign libraries: <code>libssl</code>, <code>libcrypto</code> and <code>libfixposix</code> are statically baked in. This removes the need of Deploy, when only these three foreign libraries are used.
📹 Discover <astyle="color: darkgrey; text-decoration: underline",href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">our contributor's Common Lisp video course on Udemy</a>