Towards ABCL Standalone Executables
By Didier Verna on Saturday, January 22 2011, 11:04 - Lisp - Permalink
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 actualargv
passed to the functionmain
, 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 ofInterpreter.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!
Comments
Can't you just go shell instead of going Java??
#! /bin/sh
exec abcl --noinform --noinit --nosystem --load greet.lisp -- ${1+$@}
(Notice the double hyphens I inserted)
Nope because ABCL will still eat options it recognizes as its own, even after "--". In fact, I don't think ABCL has any notion of a double hyphen. Besides, it doesn't help you build a complete application in a JAR file, which was really my point.
ABCL should obey the tokens '--' as "don't process arguments further".
Filed as [ticket #128][128]
128]: [http://trac.common-lisp.net...
Ville has [implemented the request to be able to use '--' to end the argument parsing in r13326][1]. Happy hacking!
1]: [http://trac.common-lisp.net/a...
I've updated this blog to reflect the fact that ABCL now understands the POSIX-compliant double-dash syntax, and I've also added one more trick at the end, to emulate the existence of a proper *argv0* variable.
What's Taking place i'm new to this, I stumbled upon this I have found It absolutely helpful and it has aided me out loads.
I am hoping to contribute & help other customers like its aided me.
Good job.
Good way of describing, and pleasant piece of writing to obtain data concerning my presentation subject matter, which i
am going to convey in school.
Very helpful. Thanks a lot!
writing literature review apa <a href="https://customessaywriterbyz.com/#">help writing a college essay</a> essay academic writing
writing an abstract for research paper <a href="https://researchpaperssfk.com/#">writing essay online</a> hypothesis for research paper
college essay promps <a href="https://writemypaperbuyhrd.com/#">college essay mills</a> writing literature review apa
writing a wedding speech groom <a href="https://essayhelpbgs.com/#">college admissions</a> gardening essay writing
key to writing a good essay <a href="https://essaywritingservicesjy.com/...">write papers for money</a> where can i write an essay online
my college essay <a href="https://dissertationhelpvfh.com/#">functional resume</a> algebra homework
homework help site <a href="https://thesiswritinghelpsjj.com/#">writing a conclusion</a> homework for me
pharm <a href="http://canadianonline-pharmacydazc....">pharmacy in canada</a> drug store near me
canadian pharmacies online <a href="http://xuypharmacyonline.com/">erection pills</a> drugstore online shopping
money next day loan <a href="http://daymoneygo.com/ ">payday loans in fernley nv</a> hard money bad credit loans