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