chiark / gitweb /
runlisp.c, README.org: Ignore unknown permitted Lisp systems.
[runlisp] / README.org
1 # -*-org-*-
2 #+TITLE: ~runlisp~ -- run scripts written in Common Lisp
3 #+AUTHOR: Mark Wooding
4 #+LaTeX_CLASS: strayman
5 #+LaTeX_HEADER: \usepackage{tikz, gnuplot-lua-tikz}
6 #+LaTeX_HEADER: \DeclareUnicodeCharacter{200B}{}
7 #+EXPORT_FILE_NAME: doc/README.pdf
8
9 ~runlisp~ is a small C program intended to be run from a script ~#!~
10 line.  It selects and invokes a Common Lisp implementation, so as to run
11 the script.  In this sense, ~runlisp~ is a partial replacement for
12 ~cl-launch~.
13
14 Currently, the following Lisp implementations are supported:
15
16   + Armed Bear Common Lisp (~abcl~),
17   + Clozure Common Lisp (~ccl~),
18   + GNU CLisp (~clisp~),
19   + Carnegie--Mellon Univerity Common Lisp (~cmucl~),
20   + Embeddable Common Lisp (~ecl~), and
21   + Steel Bank Common Lisp (~sbcl~).
22
23 Adding more Lisps is simply a matter of writing the necessary runes in a
24 configuration file.  Of course, there's a benefit to having a collection
25 of high-quality configuration runes curated centrally, so I'm happy to
26 accept submissions in support of any free[fn:free] Lisp implementations.
27
28 [fn:free] Here I mean free as in freedom.
29
30
31 * Writing scripts in Common Lisp
32
33 ** Basic use
34
35 The obvious way to use ~runlisp~ is in a shebang (~#!~) line at the top
36 of a script.  For example:
37
38 : #! /usr/local/bin/runlisp
39 : (format t "Hello from Lisp!~%")
40
41 Script interpreters must be named with absolute pathnames in shebang
42 lines; if your ~runlisp~ is installed somewhere other than
43 ~/usr/local/bin/~ then you'll need to write something different.
44 Alternatively, a common hack involves abusing the ~env~ program as a
45 script interpreter, because it will do a path search for the program
46 it's supposed to run:
47
48 : #! /usr/bin/env runlisp
49 : (format t "Hello from Lisp!~%")
50
51 ** Specific Lisps
52
53 Lisp implementations are not created equal -- for good reason.  If your
54 script depends on the features of some particular Lisp implementation,
55 then you can tell ~runlisp~ that it must use that implementation to run
56 your script using the ~-L~ option; for example:
57
58 : #! /usr/local/bin/runlisp -Lsbcl
59 : (format t "Hello from Steel Bank Common Lisp!~%")
60
61 If your script supports several Lisps, but not all, then list them all
62 in the ~-L~ option, separated by commas:
63
64 : #! /usr/local/bin/runlisp -Lsbcl,ccl
65 : (format t #.(concatenate 'string
66 :                          "Hello from "
67 :                          #+sbcl "Steel Bank"
68 :                          #+ccl "Clozure"
69 :                          #-(or sbcl ccl) "an unexpected"
70 :                          " Common Lisp!~%"))
71
72 It is not an error to include the name of an unrecognized Lisp system in
73 the ~-L~ option: such names are simply ignored.  This allows a script to
74 declare support for unusual or locally installed Lisp systems without
75 compromising its portability to sites where such systems are unknown, or
76 which are still running older versions of ~runlisp~ which haven't been
77 updated with the necessary configuration for those systems.
78
79 ** Embedded options
80
81 If your script requires features of particular Lisp implementations
82 /and/ you don't want to hardcode an absolute path to ~runlisp~, then you
83 have a problem.  Most Unix-like operating systems will parse a shebang
84 line into the initial ~#!~, the pathname to the interpreter program,
85 and a /single/ optional argument: any further spaces don't separate
86 further arguments: they just get included in the first argument, all the
87 way up to the end of the line.  So
88
89 : #! /usr/bin/env runlisp -Lsbcl
90 : (format t "Hello from Steel Bank Common Lisp!~%")
91
92 won't work: it'll just try to run a program named ~runlisp -Lsbcl~, with
93 a space in the middle of its name, and that's quite unlikely to exist.
94
95 To help with this situation, ~runlisp~ reads /embedded options/ from
96 your script.  Specifically, if the script's second line contains the
97 token ~@RUNLISP:~ then ~runlisp~ will parse additional options from this
98 line.  So the following will work properly.
99
100 : #! /usr/bin/env runlisp
101 : ;;; @RUNLISP: -Lsbcl
102 : (format t "Hello from Steel Bank Common Lisp!~%")
103
104 Embedded options are split at spaces properly.  Spaces can be escaped or
105 quoted in (an approximation to) the usual shell manner, should that be
106 necessary.  See the manpage for the gory details.
107
108 ** Common environment
109
110 ~runlisp~ puts some effort into making sure that Lisp scripts get the
111 same view of the world regardless of which implementation is running
112 them.
113
114 For example:
115
116   + The ~asdf~ and ~uiop~ systems are loaded and ready for use.
117
118   + The script's command-line arguments are available in
119     ~uiop:*command-line-arguments*~.  Its name can be found by calling
120     ~(uiop:argv0)~ -- though it's probably also in ~*load-pathname*~.
121
122   + The prevailing Unix standard input, output, and error files are
123     available through the Lisp ~*standard-input*~, ~*standard-output*~,
124     and ~*error-ouptut*~ streams, respectively.  (This is, alas, not a
125     foregone conclusion.)
126
127   + The keyword ~:runlisp-script~ is added to the ~*features*~ list.
128     This means that your script can tell whether it's being run from the
129     command line, and should therefore do its thing and then quit; or
130     merely being loaded into a Lisp system, e.g., for debugging or
131     development, and should sit still and not do anything until it's
132     asked.
133
134 See the manual for the complete list of guarantees.
135
136
137 * Invoking Lisp implementations
138
139 ** Basic use
140
141 A secondary use of ~runlisp~ is in build scripts for Lisp programs.  If
142 the entire project is just a Lisp library, then it's possibly acceptable
143 to just provide an ASDF system definition and expect users to type
144 ~(asdf:load-system "mumble")~ to use it.  If it's a program, or there
145 are things other than Lisp which ASDF can't or shouldn't handle --
146 significant pieces in other languages, or a Lisp executable image to
147 make and install -- then it seems sensible to make the project's main
148 build system be something language-agnostic, say Unix ~make~, and
149 arrange for that to invoke ASDF at the appropriate time.
150
151 But how should that be arranged?  It's relatively easy for a project'
152 Lisp code to support multiple Lisp implementation; but each
153 implementation wants different runes for evaluating Lisp forms from the
154 command line, and some of them don't provide an ideal environment for
155 integrating into a build system.  So ~runlisp~ provides a simple common
156 command-line interface for evaluating Lisp forms.  For example:
157
158 : $ runlisp -e '(format t "~A~%" (+ 1 2))'
159 : 3
160
161 If your build script needs to get information out of Lisp, then wrapping
162 ~format~, or even ~princ~, around forms is annoying; so ~runlisp~ has a
163 ~-p~ option which prints the values of the forms it evaluates.
164
165 : $ runlisp -e '(+ 1 2)'
166 : 3
167
168 If a form produces multiple values, then ~-p~ will print all of them, as
169 if by ~princ~, separated by spaces, on a single line:
170
171 : $ runlisp -p '(floor 5 2)'
172 : 2 1
173
174 There's also a ~-d~ option, which does the same thing as ~-p~, only it
175 prints values as if by ~prin1~.  For example,
176
177 : $ runlisp -p '"Hello, world!"'
178 : Hello, world!
179 : runlisp -d '"Hello, world!"'
180 : "Hello, world!"
181
182 In addition to evaluating forms with ~-e~, and printing their values
183 with ~-d~ and ~-p~, you can also load a file of Lisp code using ~-l~.
184
185 When ~runlisp~ is acting on ~-e~, ~-p~, and/or ~-l~ options, it's said
186 to be running in /eval/ mode, rather than its usual /script/ mode.  In
187 eval mode, it /doesn't/ set ~:runlisp-script~ in ~*features*~.
188
189 You can still insist that ~runlisp~ use a particular Lisp
190 implementation, or one of a subset of implementations, using the ~-L~
191 option mentioned above.
192
193 : $ runlisp -Lsbcl -p "(lisp-implementation-type)"
194 : "SBCL"
195
196 ** Command-line processing
197
198 When scripting a Lisp -- as opposed to running a Lisp script -- it's not
199 necessarily the case that your script knows in advance exactly what it
200 needs to ask Lisp to do.  For example, it might need to tell Lisp to
201 install a program in a particular directory, determined by Autoconf.
202 While it's certainly /possible/ to quote such data and splice them into
203 Lisp forms, it's more convenient to pass them in separately.  So
204 ~runlisp~ ensures that the command-line options are available to Lisp
205 forms via ~uiop:*command-line-arguments*~, as they are to a Lisp script.
206
207 : $ runlisp -p "uiop:*command-line-arguments*" one two three
208 : ("one" "two" "three")
209
210 When running Lisp forms like this, ~(uiop:argv0)~ isn't very
211 meaningful.  (Currently, it reveals the name of the script which
212 ~runlisp~ uses to implement this feature.)
213
214
215 * Configuring =runlisp=
216
217 ** Where =runlisp= looks for configuration
218
219 You can influence which Lisp implementations are chosen by ~runlisp~ by
220 writing configuration files, and/or setting environment variables.
221
222 The ~runlisp~ program looks for configuration in a number of places.
223
224   + There's a system-global directory ~SYSCONFDIR/runlisp/runlisp.d/~.
225     All of the files in this directory named ~SOMETHING.conf~ are read,
226     in increasing lexicographical order by name.  The package comes with
227     a file ~0base.conf~ intended to be read first, so that it can be
228     overridden if necessar.  This sets up basic definitions, and defines
229     the necessary runes for those Lisp implementations which are
230     supported `out of the box'.  New Lisp packages might come with
231     additional files to drop into this directory.
232
233   + There's a system-global file ~SYSCONFDIR/runlisp/runlisp.conf~ which
234     is intended to be edited by the system administrator to account for
235     any local quirks.  This is read /after/ the directory, which is
236     intended to be used by distribution packages, so that the system
237     administrator can override them.
238
239   + Users can create files ~$HOME/.runlisp.conf~ and/or
240     ~$HOME/.config/runlisp.conf~[fn:xdg-config] in their home
241     directories to add support for privately installed Lisp systems, or
242     to override settings made by earlier configuration files.
243
244 But configuration files generally look like =.ini=-style files.  A line
245 beginning with a semicolon ~;~ is a comment and is ignored.  Most lines
246 are assignments, which look like
247 #+BEGIN_QUOTE
248 /name/ ~=~ /value/
249 #+END_QUOTE
250 and assignments are split into sections by section headers in square
251 brackets:
252 #+BEGIN_QUOTE
253 ~[~\relax{}/section/\relax{}~]~
254 #+END_QUOTE
255 The details of the configuration syntax are complicated, and explained
256 in the *runlisp.conf* manpage.
257
258 Configuration options can also be set on the command line, though the
259 effects are subtly different.  Again, see the manual pages for details.
260
261 [fn:xdg-config] More properly, in ~$XDG_CONFIG_HOME/runlisp.conf~, if
262 you set that.
263
264
265 ** Deciding which Lisp implementation to use
266
267 The ~prefer~ option specifies a /preference list/ of Lisp
268 implementations.  The value is a list of Lisp implementation names, as
269 you'd give to ~-L~, separated by commas and/or spaces.  If the
270 environment variable ~RUNLISP_PREFER~ is set, then this overrides any
271 value found in the configuration files.  So your ~$HOME/.runlisp.conf~
272 file might look like this:
273
274 : ;;; -*-conf-*-
275 :
276 : prefer = sbcl, clisp
277
278 When deciding which Lisp implementation to use, ~runlisp~ works as
279 follows.  It builds a list of /acceptable/ Lisp implementations from the
280 ~-L~ command-line option, and a list of /preferred/ Lisp implementations
281 from the ~prefer~ configuration option (or environment variable).  If
282 there aren't any ~-L~ options, then it assumes that /all/ Lisp
283 implementations are acceptable; if no ~prefer~ option is set then it
284 assumes that /no/ Lisp implementations are preferred.  It then works
285 through the preferred list in order: if it finds an implementation which
286 is installed and acceptable, then it uses that one.  If that doesn't
287 work, then it works through the acceptable implementations that it
288 hasn't tried yet, in order, and if it finds one of those that's
289 installed, then it runs that one.  Otherwise it reports an error and
290 gives up.
291
292
293 ** Supporting new Lisp implementations
294
295 ~runlisp~ tries hard to make adding support for a new Lisp as painless
296 as possible.  An awkward Lisp will of course cause trouble, but
297 ~runlisp~ itself is easy.
298
299 As a simple example, let's add support for the 32-bit version of
300 Clozure\nbsp{}CL.  The source code for Clozure\nbsp{}CL easily builds
301 both 32- and 64-bit binaries in either 32- or 64-bit userlands, and one
302 might reasonably want to use the 32-bit CCL for some reason.  The
303 following configuration stanza is sufficient
304
305 : [ccl32]
306 : @PARENTS = ccl
307 : command = ${@ENV:CCL32?ccl32}
308
309   + The first line heads a configuration section, providing the name
310     which will be used for this Lisp implementation, e.g., in ~-L~
311     options or ~prefer~ lists.
312
313   + The second line tells ~runlisp~ that configuration settings not
314     found in this section should be looked up in the ~ccl~ section
315     instead.
316
317   + The third line defines the command to be used to invoke the Lisp
318     system.  It tries to find an environment variable named ~CCL32~,
319     falling back to looking up ~ccl32~ in the path otherwise.
320
321 And, err..., that's it.  The ~@PARENTS~ setting uses the detailed
322 command-line runes for ~ccl~, so they don't need to be written out
323 again.
324
325 That was rather anticlimactic, because all of the work got done
326 somewhere else.  So let's look at a complete example: Steel Bank Common
327 Lisp.  (SBCL's command-line interface is well thought-out, so this is an
328 ideal opportunity to explain how ~runlisp~ configuration works, without
329 getting bogged down in the details of fighting less amenable Lisps.)
330
331 The provided ~0base.conf~ file defines SBCL as follows.
332
333 : [sbcl]
334
335 : command = ${@ENV:SBCL?sbcl}
336 : image-file = ${@NAME}+asdf.core
337
338 : run-script =
339 :         ${command} --noinform
340 :                 $?@IMAGE{--core "${image-path}" --eval "${image-restore}" |
341 :                          --eval "${run-script-prelude}"}
342 :                 --script "${@SCRIPT}"
343
344 : dump-image =
345 :         ${command} --noinform --no-userinit --no-sysinit --disable-debugger
346 :                 --eval "${dump-image-prelude}"
347 :                 --eval "(sb-ext:save-lisp-and-die \"${@IMAGENEW|q}\")"
348
349 Let's take this in slightly larger pieces.
350
351   + We see the ~[sbcl]~ section heading, and the ~command~ setting
352     again.  These should now be unsurprising.
353
354   + There's no ~@PARENTS~ setting, so by default the ~sbcl~ section
355     inherits settings from the ~@COMMON~ section, defined in
356     ~0base.conf~.  We shall use a number of definitions from this
357     section.
358
359   + The ~image-file~ gives the name of the custom image file to look for
360     when trying to start SBCL, but not the directory.  (The directory is
361     named by the ~image-dir~ configuration setting.)  The image file
362     will be named ~sbcl+asdf.core~, but this isn't what's written.
363     Instead, it uses ~${@NAME}~, which is replaced by the name of the
364     section being processed.  When we're running SBCL, this does the
365     same thing; but if someone wants to configure a new ~foo~ Lisp and
366     set ~@PARENTS~ to ~sbcl~, then the image file for ~foo~ will be
367     named ~foo+asdf.core~ by default.  You needn't take such care when
368     configuring Lisp implementations for your own purposes, but it's
369     important for configurations which will be widely used.
370
371   + The ~run-script~ setting explains how to get SBCL to run a script.
372     This string is broken into words at (unquoted) spaces.
373
374     The syntax ~$?VAR{CONSEQ|ALT}~ means: if a configuration setting
375     ~VAR~ is defined, then expand to ~CONSEQ~; otherwise, expand to
376     ~ALT~.  In this case, if the magic setting ~@IMAGE~ is defined, then
377     we add the tokens ~--core "${image-path}" --eval "${image-restore}"~
378     to the SBCL command line; otherwise, we add ~--eval
379     "${run-script-prelude}"~.  The ~@IMAGE~ setting is defined by
380     ~runlisp~ only if (a)\nbsp{}a custom image was found in the correct
381     place, and (b)\nbsp{}use of custom images isn't disabled on its
382     command line.
383
384     The ~${image-path}~ token expands to the full pathname to the custom
385     image file; ~image-restore~ is a predefined Lisp expression to be
386     run when starting from a dumped image (e.g., to get ASDF to refresh
387     its idea of which systems are available).
388
389     The ~run-script-prelude~ is another (somewhat involved) Lisp
390     expression which sets up a Lisp environment suitable for running
391     scripts -- e.g., by arranging to ignore ~#!~ lines, and pushing
392     ~:runlisp-script~ onto ~*features*~.
393
394     Finally, regardless of whether we're using a custom or vanilla
395     image, we add the tokens ~--script "${@SCRIPT}"~ to the command
396     line.  The ~${@SCRIPT}~ token is replaced by the actual script
397     pathname.  ~runlisp~ then appends further arguments from its own
398     command line and runs the command.  (For most Lisps, ~uiop~ needs a
399     ~--~ marker before the user arguments, but not for SBCL.)
400
401   + Finally, ~dump-image~ defines a command line for dumping a custom
402     images.  The ~dump-image-prelude~ setting is a Lisp expression for
403     setting up a Lisp so that it will be in a useful state when dumped:
404     it's very similar to ~run-script-prelude~, and is built out of many
405     of the same pieces.
406
407     The thing we haven't seen before is ~${@IMAGENEW|q}~.  The
408     ~@IMAGENEW~ setting is defined by the ~dump-runlisp-image~ program
409     to name the file in which the new image should be
410     saved.[fn:image-rename]  The ~|q~ `filter' is new: it means that the
411     filename should be escaped suitable for inclusion in a Lisp quoted
412     string, by prefixing each ~\~ or ~"~ with a ~\~.
413
414 That's more or less all there is.  SBCL is a particularly simple
415 example, but mostly because other Lisp implementations require fancier
416 stunts /at the Lisp level/.  The ~runlisp~-level configuration isn't any
417 more complicated than SBCL.
418
419 [fn:image-rename] ~dump-runlisp-image~ wants to avoid clobbering an
420 existing image with a half-finished one, so it tries to arrange for the
421 new image to be written to a different file, and then renames it once
422 it's been created successfully.)
423
424
425 * What's wrong with =cl-launch=?
426
427 The short version is that ~cl-launch~ is slow and inconvenient.
428 ~cl-launch~ is a big, complicated Common Lisp/Bourne shell polyglot
429 which tries to do everything but doesn't quite succeed.
430
431 ** It's slow.
432
433 I took a trivial Lisp script:
434
435 : (format t "Hello from ~A!~%~
436 :            Script = `~A'~%~
437 :            Arguments = (~{`~A'~^, ~})~%"
438 :         (lisp-implementation-type)
439 :         (uiop:argv0)
440 :         uiop:*command-line-arguments*)
441
442 I timed how long it took to run on all of ~runlisp~'s supported Lisp
443 implementations, and compared them to how long ~cl-launch~ took: the
444 results are shown in table [[tab:runlisp-vanilla]].  ~runlisp~ is /at least/
445 two and half times faster at running this script than ~cl-launch~ on all
446 implementations except Clozure\nbsp{}CL[fn:slow-ccl], and approaching
447 four and a half times faster on SBCL.
448
449 #+CAPTION: ~cl-launch~ vs ~runlisp~ (with vanilla images)
450 #+NAME: tab:runlisp-vanilla
451 #+ATTR_LATEX: :float t :placement [tbp]
452 |------------------+-------------------+-----------------+----------------------|
453 | *Implementation* | *~cl-launch~ (s)* | *~runlisp~ (s)* | *~runlisp~ (factor)* |
454 |------------------+-------------------+-----------------+----------------------|
455 | ABCL             |            7.3378 |          2.6474 | 2.772                |
456 | Clozure CL       |            1.2888 |          0.9742 | 1.323                |
457 | GNU CLisp        |            1.2405 |          0.2703 | 4.589                |
458 | CMU CL           |            0.9521 |          0.3097 | 3.074                |
459 | ECL              |            0.8020 |          0.3236 | 2.478                |
460 | SBCL             |            0.3205 |          0.0874 | 3.667                |
461 |------------------+-------------------+-----------------+----------------------|
462 #+TBLFM: $4=$2/$3;%.3f
463
464 But this is using the `vanilla' Lisp images installed with the
465 implementations.  ~runlisp~ by default builds custom images for most
466 Lisp implementations, which improves startup performance significantly;
467 see table [[tab:runlisp-custom]].  (I don't currently know how to build a
468 useful custom image for ABCL.  ~runlisp~ does build a custom image for
469 ECL, but it doesn't help significantly.)  These results are summarized
470 in figure [[fig:lisp-graph]].
471
472 #+CAPTION: ~cl-launch~ vs ~runlisp~ (with custom images)
473 #+NAME: tab:runlisp-custom
474 #+ATTR_LATEX: :float t :placement [tbp]
475 |------------------+-------------------+-----------------+----------------------|
476 | *Implementation* | *~cl-launch~ (s)* | *~runlisp~ (s)* | *~runlisp~ (factor)* |
477 |------------------+-------------------+-----------------+----------------------|
478 | ABCL             |            7.3378 |          2.7023 | 2.715                |
479 | Clozure CL       |            1.2888 |          0.0371 | 34.739               |
480 | GNU CLisp        |            1.2405 |          0.0191 | 64.948               |
481 | CMU CL           |            0.9521 |          0.0060 | 158.683              |
482 | ECL              |            0.8020 |          0.3275 | 2.449                |
483 | SBCL             |            0.3205 |          0.0064 | 50.078               |
484 |------------------+-------------------+-----------------+----------------------|
485 #+TBLFM: $4=$2/$3;%.3f
486
487 #+CAPTION: Comparison of ~runlisp~ and ~cl-launch~ times
488 #+NAME: fig:lisp-graph
489 #+ATTR_LATEX: :float t :placement [tbp]
490 [[file:doc/lisp-graph.tikz]]
491
492 Unlike ~cl-launch~, with some Lisp implementations at least, ~runlisp~
493 startup performance is usefully comparable to other popular scripting
494 language implementations.  I wrote similarly trivial scripts in a number
495 of other languages, and timed them; the results are tabulated in table
496 [[tab:runlisp-interp]] and graphed in figure [[fig:interp-graph]].
497
498 #+CAPTION: ~runlisp~ vs other interpreters
499 #+NAME: tab:runlisp-interp
500 #+ATTR_LATEX: :float t :placement [tbp]
501 |------------------------------+-------------|
502 | *Implementation*             | *Time (ms)* |
503 |------------------------------+-------------|
504 | Clozure CL                   |        37.1 |
505 | GNU CLisp                    |        19.1 |
506 | CMU CL                       |         6.0 |
507 | SBCL                         |         6.4 |
508 |------------------------------+-------------|
509 | Perl                         |         1.1 |
510 | Python                       |         6.8 |
511 |------------------------------+-------------|
512 | Debian Almquist shell (dash) |         1.2 |
513 | GNU Bash                     |         1.5 |
514 | Z Shell                      |         3.1 |
515 |------------------------------+-------------|
516 | Tiny C (compile & run)       |         1.6 |
517 | GCC (precompiled)            |         0.6 |
518 |------------------------------+-------------|
519
520 #+CAPTION: Comparison of ~runlisp~ and other script interpreters
521 #+NAME: fig:interp-graph
522 #+Attr_latex: :float t :placement [tbp]
523 [[file:doc/interp-graph.tikz]]
524
525 (All the timings in this section were performed on the same 2020 Dell
526 XPS13 laptop running Debian `buster'.  The tools used to make the
527 measurements are included in the source distribution, in the ~bench/~
528 subdirectory.)
529
530 [fn:slow-ccl] I don't know why Clozure\nbsp{}CL shows such a small
531 difference here.
532
533 ** It's inconvenient
534
535 ~cl-launch~ has this elaborate machinery which reads shell script
536 fragments from various places and sets variables like ~$LISPS~, but it
537 doesn't quite work.
538
539 Unlike other scripting languages such as Perl or Python, Common Lisp has
540 lots of implementations, and they all have various unique features (and
541 bugs) which a script might rely on (or need to avoid).  Also, a user
542 might have preferences about which Lisps to use.  ~cl-launch~'s approach
543 to this problem is a ~system_preferred_lisps~ shell function which can
544 be used in ~~/.cl-launchrc~ to select a Lisp system for a particular
545 `software system', though this notion doesn't appear to be well-defined,
546 but this all works by editing a single ~$LISPS~ shell variable.  By
547 contrast, ~runlisp~ has a ~-L~ option with which scripts can specify the
548 Lisp systems they support (in a preference order), and a ~prefer~
549 configuration setting with which users can express their own
550 preferences: ~runlisp~ will never choose a Lisp system which the script
551 can't deal with, but it will respect the user's relative preferences.
552
553 Also, ~cl-launch~ is a monolith.  Adding a new Lisp implementation to
554 it, or changing how a particular implementation is invoked, is rather
555 involved.  By contrast, ~runlisp~ makes this remarkably easy, as
556 described in [[Supporting new Lisp implementations]].
557
558 ** It doesn't establish a (useful) common environment
559
560 A number of Lisp systems are annoyingly deficient in their handling of
561 scripts.
562
563 For example, when GNU CLisp's ~-x~ option is used, it rebinds
564 ~*standard-input*~ to an internal string stream holding the expression
565 passed in on the command line, leaving the process's actual stdin nearly
566 impossible to access.
567
568 : $ date | cl-launch -l sbcl -i "(princ (read-line nil nil))" # expected
569 : Sun  9 Aug 14:39:10 BST 2020
570 : $ date | cl-launch -l clisp -i "(princ (read-line nil nil))" # bug!
571 : NIL
572
573 As another example, Armed Bear Common Lisp doesn't seem to believe in
574 the stderr stream: when it starts up, ~*error-ouptut*~ is bound to the
575 standard output, just like ~*standard-output*~.  Also, ~cl-launch~
576 loading ASDF causes a huge number of ~style-warning~ messages to be
577 written to stdout, making ABCL pretty much useless for writing filter
578 scripts.
579
580 : $ cl-launch -l sbcl -i '(progn
581 :                           (format *standard-output* "output~%")
582 :                           (format *error-output* "error~%"))' \
583 :   > >(sed 's/^/stdout: /') 2> >(sed 's/^/stderr: /')
584 : stdout: output
585 : stderr: error
586 : $ cl-launch -l abcl -i '(progn
587 :                           (format *standard-output* "output~%")
588 :                           (format *error-output* "error~%"))' \
589 :   > >(sed 's/^/stdout: /') 2> >(sed 's/^/stderr: /')
590 : [1813 lines of compiler warnings tagged `stdout:']
591 : stdout: output
592 : stdout: error
593
594 ~runlisp~ takes care of all of this, providing a basic but useful common
595 level of shell integration for all its supported Lisp implementations.
596 In particular:
597
598   + It ensures that the standard Unix `stdin', `stdout', and `stderr'
599     file descriptors are hooked up to the Lisp ~*standard-input*~,
600     ~*standard-output*~, and ~*error-output*~ streams.
601
602   + It ensures that starting a script doesn't write a deluge of
603     diagnostic drivel.
604
605 The complete details are given in ~runlisp~'s manpage.
606
607 ** Why might one prefer =cl-launch= anyway?
608
609 On the other hand, ~cl-launch~ is well established and full-featured.
610
611 ~cl-launch~ compiles scripts before trying to run them, so they'll run
612 faster on Lisps which use an interpreter by default.  It has a caching
613 feature so running a script a second time doesn't need to recompile it.
614 If your scripts are compute-intensive and benefit from ahead-of-time
615 compilation then maybe ~cl-launch~ is preferable.
616
617 ~cl-launch~ supports more Lisp systems.  I only have six installed on my
618 development machine at the moment, so those are the ones that ~runlisp~
619 supports.  If you want your scripts to be able to run on other Lisps,
620 then ~cl-launch~ is the way to do that.  Of course, I welcome patches to
621 help ~runlisp~ support other free Lisp implementations.  ~cl-launch~
622 also supports proprietary Lisps: I have very little interest in these,
623 so if you want to run scripts using Allegro or LispWorks then
624 ~cl-launch~ is your only choice.