UPDATE: ABCL now supports the POSIX-compliant use of --, so I've modified this blog to reflect that. I've also added a final trick to emulate the existence of a proper argv[0].

Creating standalone executables in Common Lisp is already a portability minefield, as the standard doesn't specify anything in that regard. The situation is even worse with ABCL because it is a Java-based implementation of Common Lisp, and Java has no notion of standalone executable at all. In this article, we will try to figure out how far we can go in that direction nevertheless. This work comes from my wish to port Clon to ABCL. In order to make it work, ABCL itself needed some changes, so if you want to try out the code provided below, beware that you need at least revision 13156 from the ABCL Subversion trunk (or version 0.24.0). Let's start by writing a great Common Lisp application for ABCL, and call it greet.lisp:

(defun main ()
  (format t "Hello,~{ ~A~}!~%" extensions:*command-line-argument-list*)
  (extensions:exit :status 0))
 
(main)

As you guessed, extensions:*command-line-argument-list* stores the list of command-line options passed to the program. Now, let's try it out:

$ abcl --noinform --noinit --nosystem --load greet.lisp John Doe
Hello, John Doe!

So far so good: ABCL-specific options were eaten by ABCL. Not very standalone however. Let's create a wrapper script around that, and call it greet:

#! /bin/sh
exec abcl --noinform --noinit --nosystem --load greet.lisp ${1+$@}

In fact, we have a problem:

$ ./greet --help me
Parameters:
--help displays this help
--noinform suppresses the printing of version info
--eval <form> evaluates the <form> before initializing REPL
--load <file> loads the file <file> before initializing REPL
--load-system-file <file> loads the system file <file> before initializing REPL
--batch enables batch mode. The --load, --load-system-file and --eval parameters are handled, and abcl exits without entering REPL
--noinit suppresses loading a .abclrc startup file
--nosystem suppresses loading the system startup file

Woops! We were expecting to get Hello, --help me! weren't we? In other words, our own command-line options may conflict with ABCL's which is not nice. One thing we can do with a recent ABCL version is to use -- to separate ABCL's own command-line arguments from ours. Our script becomes:

#! /bin/sh
exec abcl --noinform --noinit --nosystem --load greet.lisp -- ${1+$@}

And we now get this:

$ ./greet --help me
Hello, --help me!

This is better, although we're not actually very standalone yet: our Lisp file is a separate beast, and the abcl thing is also just a wrapper script (see below). If we want to be more standalone, we will need to go the Java way.

The first thing to understand is that there's actually no such thing as an ABCL executable. ABCL is packed into a JAR (Java archive) file, a zip file, really, and what the wrapper does is simply to call java -jar /path/to/abcl/abcl.jar options... The JAR file contains a Main class with a main function the purpose of which is to launch a Lisp interpreter. This is done by calling the method Interpreter.createDefaultInstance, which, amongst other things, handles the command-line options. There is also a method called Interpreter.createInstance which does not handle the command-line at all, so it seems that this is the one we could use, provided that we find a way to set extensions:*command-line-argument-list* manually.

The following code does exactly this. Let's call it Greet.java.

import org.armedbear.lisp.*;
 
public class Greet
{
    public static void main (final String[] argv)
    {
	Runnable r = new Runnable ()
	    {
		public void run()
		{
		    try
			{
			    LispObject cmdline = Lisp.NIL;
			    for (String arg : argv)
				cmdline = new Cons (arg, cmdline);
			    cmdline.nreverse ();
			    Lisp._COMMAND_LINE_ARGUMENT_LIST_.setSymbolValue (cmdline);
 
			    Interpreter.createInstance ();
			    Load.load ("greet.lisp");
			}
		    catch (ProcessingTerminated e)
			{
			    System.exit (e.getStatus ());
			}
		}
	    };
 
	new Thread (null, r, "interpreter", 4194304L).start();
    }
}

Our Greet class mimics what ABCL's Main class does, only with some tweaks of our own:

  • we build the value of extensions:*command-line-argument-list* manually from the actual argv passed to the function main, by consing equivalent Lisp objects and then reversing the list. That way, we completely bypass ABCL's options processing code.
  • as mentioned earlier, we use Interpreter.createInstance instead of Interpreter.createDefaultInstance.
  • we also take the opportunity to load our lisp file directly with the function Load.load.

Finally, the try/catch construct intercepts calls to extensions:exit and effectively leaves the Java environment.

Let's compile this beast now:

$ javac -cp  /path/to/abcl/abcl.jar Greet.java

The -cp option to javac, the Java compiler, tells it where to find ABCL classes. This command gives us a Greet.class file, but also a second one named Greet$1.class. That's because our code creates an "anonymous inner class", whatever that means, in the Java jargon. Now, how do we bypass ABCL's main function and use or own instead? This is actually very simple. The Java command-line lets you specify (read: override) the name of the class in which to find the main function. So it turns out that we don't even have to modify ABCL itself for this to work. We can simply go:

$ java -cp  /path/to/abcl/abcl.jar:. Greet John Doe
Hello, John Doe!

and it works. By the way, we can also go:

$ java -cp  /path/to/abcl/abcl.jar:. Greet --help me
Hello, --help me!

and see that it still works. Since ABCL doesn't process its command-line anymore, there's no question about -- or whatever. Note that this time, we need to add the current directory to the class path option in order for Java to find the files Greet.class and Greet$1.class. Note also that with our custom interepreter, there's no more need for all those "standalone" options like --noinit, --noinform etc. Wrapping this in a shell script is left as an exercise, although not a difficult one.

Still, we're not quite standalone yet. For example, it's not cool to have these two Greet class files floating around like this. All other class files are in the ABCL JAR file, so we can think of adding ours in too. If we want to do that, we also need to state that the main class in the JAR file should be our Greet class, not ABCL's Main one. This can be done by modifying the so-called JAR manifest.

Let's create a manifest file first and call it Greet.txt:

Main-Class: Greet

It says what it says: our application's Main class (where the main function is) is Greet. Now let's make a copy of abcl.jar and add our class file to it:

$ cp /path/to/abcl/abcl.jar greet.jar
$ jar umf Greet.txt greet.jar Greet.class Greet$1.class
Jan 22, 2011 5:50:32 PM java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Main-Class.
Ensure that the manifest does not have duplicate entries, and
that blank lines separate individual sections in both your
manifest and in the META-INF/MANIFEST.MF entry in the jar file.

The u stands for "update", the m stands for "manifest" and the f stands for target (JAR) "file". The manifest and JAR files must appear in the same order as their corresponding options. Don't worry about the warning. That's because we're changing the Main class (ABCL had one already).

Let's try it out:

$ java -jar  greet.jar John Doe
Hello, John Doe!

Cool. But what about the Lisp file? It's still floating around. As a matter of fact, the JAR file may contain anything you want, not just class files. ABCL itself has a bunch of lisp files in its JAR file already. So we can put it in there just as well:

$ jar uf greet.jar greet.lisp

The last question is: how to have ABCL load a lisp file that's in its own JAR? The answer is: dig into the code and figure out that there is a method called Load.loadSystemFile which does just that. ABCL uses it to load its own boot.lisp for instance. We hence need to modify our main function and replace the line that calls Load.load with this one:

Load.loadSystemFile ("/greet.lisp", false, false, false);

Note the leading slash. It is needed because we have stored greet.lisp at the root of the JAR file. Let's update our JAR file once again:

$ javac -cp /path/to/abcl/abcl.jar Greet.java
$ jar uf greet.jar Greet.class Greet$1.class

And voilà! What you get this time is your application completely packed up in the JAR file. You can move this file around, distribute it... everything's inside.

Here's a final additional trick and I think we're done. As you know, ABCL stores the command-line argument list in extensions:*command-line-argument-list*. One potential problem is that there's no notion of a program name (argv[0]) in ABCL (the program is in fact always Java). Since we're going standalone, however, maybe we would like to have that. Something like, say, a variable named extensions:*argv0*. So here's a way to do it (Clon does this as part of its built-in dump facility): in the Greet class, replace the line

Interpreter.createInstance ();

with this:

Interpreter interpreter = Interpreter.createInstance ();
interpreter.eval ("(defvar extensions::*argv0* \"greet\")");
interpreter.eval ("(export 'extensions::*argv0* 'extensions)");

So that's it I guess. It seems to me that this is as far as you can go in the direction of standalone ABCL executables. Of course, this example is overly simple. We didn't try to incorporate full ASDF systems in the archive, and stuff. But you get the idea... I've attached my custom interpreter class below if you want to play with it.

Have fun!