Subject: Re: `Standalone Executables'
From: rpw3@rpw3.org (Rob Warnock)
Date: Sat, 07 Feb 2004 07:20:13 -0600
Newsgroups: comp.lang.lisp
Message-ID: <ne2cnUA7sqeQe7ndXTWc-w@speakeasy.net>
Wade Humeniuk  <whumeniu-delete-this-antispam-device@telus.net> wrote:
+---------------
| Joe Marshall wrote:
| > At regular intervals on comp.lang.lisp someone asks how to create
| > 'standalone executables' Common Lisp.  Rather than enter into a
| > technical discussion of what is meant by a 'standalone executables'...
| 
| Yes, entering into technical discussions has little affect.
| IMO, a direct reference to a tutorial example should be given.
...
| for example:
| CMUCL startup with a #! executable "hello World" example.
+---------------

Yes, some tutorial material on that should be helpful, since as distributed
CMUCL does not come with a built-in reader macro for "#!" [even though most
of us using CMUCL for any time quickly build a core image containing one!].

Fortunately, most of the Unix/Linux operating systems assume a /bin/sh
script if the script does not start with "#!" (or "#", for "csh" users),
so with the minimal cost of a fork/exec of /bin/sh, the following idiom[1]
suffices for most scripting needs:

    % cat foo
    ":" ; exec cmucl -quiet -noinit -load "`which $0`" ${1+"$@"}
    (format t "Hello, world!~%")
    (quit)
    % time foo
    Hello, world!
    0.009u 0.012s 0:00.03 33.3%     592+3636k 0+0io 0pf+0w
    % 

19 milliseconds -- not too shabby! [Well, on a 1.8 GHz Athlon... ;-} ]

Add a few "convenience features", and you get this:

    % cat foo2
    ":" ; exec cmucl -quiet -noinit -load "`which $0`" ${1+"$@"}

    ;;; Provide script name and command-line args [if any] to script:
    (defvar *script-name* (nth 4 *command-line-strings*)) ; See [2], below.
    (defvar *script-args* (nthcdr 5 *command-line-strings*))
    (defvar *script-interactive-repl* nil)

    (format t "Hello, world!~%")
    (format t "My name = '~a'~%" *script-name*)
    (loop for i from 1
	  and a in *script-args*
      do (format t "arg[~a] = '~a'~%" i a))

    (unless *script-interactive-repl*       ; set when debugging.
      (quit))
    % foo2 bar baz 'many words here'
    Hello, world!
    My name = 'foo2'
    arg[1] = 'bar'
    arg[2] = 'baz'
    arg[3] = 'many words here'
    % 

And as long as I'm on the topic, it seems I made a mistake in my posting
the other day when I showed a script "header" for FASL files. Turns out it
was written for an older version of CMUCL in which READ-LINE didn't complain
if given a binary stream. But CMUCL-18e's READ-LINE *does* bitch and moan
(arguably rightfully), so here is an updated version (tested) that works
nicely with CMUCL-18e:

    % cat bin/cmucl-fasl-script-header 
    ":" ; exec cmucl -quiet -noinit -load "`which $0`" ${1+"$@"}

    ;;;; Hack to allow CMUCL FASL files to run as shell scripts.
    ;;;;
    ;;;; USAGE: cat {this_file} foo1.x86f ... fooN.x86f > foo ; chmod +x foo
    ;;;; The last "fooN.x86f" must end with a top-level form which starts the
    ;;;; application [and then should fall-through, *not* exit! -- see below].

    ;;; Provide script name and command-line args [if any] to FASL file(s):
    (defvar *script-name* (nth 4 *command-line-strings*))
    (defvar *script-args* (nthcdr 5 *command-line-strings*))
    (defvar *script-interactive-repl* nil)
    (setf ext::*complain-about-illegal-switches* nil)

    (with-open-file (s *script-name* :element-type '(unsigned-byte 8))
      (loop for c = (read-byte s)   ; Eat header text from stream through
	    until (= c 90)          ;  final "magic character" [uppercase "z",
	finally (read-byte s))      ;  see below] and the newline which
      (load s :contents :binary)    ;  follows, then pass the rest to FASLOAD.
      (if *script-interactive-repl* ; If set later, start up a Lisp top-level.
	(continue) ; Keep initial LOAD of script from falling into FASL files.
	(quit)))                    ; So non-REPL case exits cleanly.

    ;;;; NOTA BENE: The end of this line is magic: Z
    % 

which is used this way:

    % cat foo3.lisp
    (format t "Hello, world!~%")
    (format t "My name = '~a'~%" *script-name*)
    (loop for i from 1
	  and a in *script-args*
      do (format t "arg[~a] = '~a'~%" i a))
    % cmucl-compile foo3.lisp
    ...compiler chatter...
    ; foo3.x86f written.
    ; Compilation finished in 0:00:00.
    % cat cmucl-fasl-script-header foo3.x86f >foo3
    % chmod +x foo3
    % ls -l foo3
    -rwxr-xr-x  1 rpw3  rpw3  2714 Feb  7 05:00 foo3
    % foo3 what a "really fun" hack.
    Hello, world!
    My name = 'foo3'
    arg[1] = 'what'
    arg[2] = 'a'
    arg[3] = 'really fun'
    arg[4] = 'hack.'
    %


-Rob

[1] Which I first learned from the PLT scheme, crowd. Thanks guys!

[2] CMUCL experts may wonder why I didn't use the "command-line-switch"
    routines here, e.g., (car (get-command-line-switch "load")) instead
    of (nth 4 *command-line-strings*)) for *script-name*, etc., and the
    (setf ext::*complain-about-illegal-switches* nil). The reason is that
    the above can easily be emulated in CLISP and other CLs, and is more
    similar to what the user expects coming from a C or Perl environment.
    And the SETF makes it easy to use application-specific options, e.g.:

      % foo3 -v -o bar bar.in
      Hello, world!
      My name = 'foo3'
      arg[1] = '-v'
      arg[2] = '-o'
      arg[3] = 'bar'
      arg[4] = 'bar.in'
      % 

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607