We will attempt to make a simple GUI app that we'll be able to redistribute and launch as a standalone app. It won't do anything useful – it'll just show a window with a “Hello, world” label. This is a step-by-step guide.
To build our GUI we'll use the cl-gtk2 library.
First of all, we should load the SLIME and load cl-gtk2:
(asdf:oos 'asdf:load-op :cl-gtk2-gtk)
Then we'll create the hello-world.lisp
source file with the following content:
(defpackage :hello-world
(:use :cl :gobject :gtk)
(:export :main :run))
(in-package :hello-world)
(defun main ()
(within-main-loop
(let ((w (make-instance 'gtk-window :title "Hello, world"))
(l (make-instance 'label :label "Hello, world!")))
(container-add w l)
(connect-signal w "destroy" (lambda (w)
(declare (ignore w))
(gtk-main-quit)))
(widget-show w))))
(defun run ()
(main)
(join-main-thread))
This code defines the hello-world
package which has two functions defined: main
and run
.
The main
function is used during development – it will run the app in the background thread when run from SLIME REPL (the macro within-main-loop
is responsible for this), and the run
function is used when the app is run by itself, without SLIME.
The join-main-thread
function awaits the main Gtk+ event loop to finish. When the window is closed and receives the destroy
signal, this loop will be finished by the (gtk-main-quit)
call.
We can test this app straight from the SLIME:
(hello-world:main)
After evaluating this thread in REPL, a new background thread with the Gtk+ event loop will start to run, and the control will return to the SLIME REPL.
Now we will create a program that can be executed as a normal standalone application.
We will start by defining an ASDF system for our app.
For this, we will create the hello-world.asd
file in the same directory that contains hello-world.lisp
with the following content:
(defsystem :hello-world
:name "hello-world"
:components ((:file "hello-world"))
:depends-on (:cl-gtk2-gtk))
This definition specifies how to build the program:
cl-gtk2-gtk
systemhello-world.lisp
(the .lisp
extension is appended automatically).Usually, ASDF is used to define systems that contain libraries and that are placed in the system-wide directory /usr/share/common-lisp/systems
. In this case, however, a system is defined for an application.
Using the cl-launch, we can turn this definition into a runnable binary.
First, we need to install the cl-launch
. In Gentoo Linux, we would add the lisp-overlay
and install cl-launch from it:
layman -a lisp
emerge dev-lisp/cl-launch
In other Linux distros we can use the ASDF-INSTALL to install it into a system-wide directory or a home directory:
(require :asdf-install)
(asdf-install:install :cl-launch)
cl-launch contains the cl-launch.sh
shell script which is used to prepare Lisp apps to run as normal apps.
First, we will create the Lisp image with our app with all of its dependencies. Unless we make an image, we will have to either compile the cl-gtk2
library from source or load it from previously compiled fasl
-files. Both of those are quite cumbersome and slow (even just loading fasl-files takes on the order of 30 seconds). But during development creating an image is not necessary, since everything is loaded just one time and the startup.
So, in order to create a Lisp image, cd
into the source directory and enter the following command:
cl-launch.sh -s hello-world -d hello-world-image
cl-launch
will load the specified system (hello-world
in this case) with all its dependencies and saves the image into the hello-world-image
file.
If several Lisp implementations are available, then we can choose what to use:
cl-launch.sh --lisp sbcl -s hello-world -d hello-world-image
Next, we will use cl-launch
to create a shell script to launch the app from a freshly made image:
cl-launch.sh -m hello-world-image -i '(hello-world:run)' -o hello-world
(if we would have multiple Lisps installed, we can also add the --lisp
argument)
As a result, a new hello-world
script is created that can be used to launch our app.
Let's try to run it:
./hello-world
Almost immediately we will see a new window popping up:
The launch delay is quite acceptable: the window appeared almost instantly.
But this method of creating an executable has a drawback – the size of the resulting image is quite noticeable. In my case (SBCL on 64-bit Linux) the size is 64 megabytes. The biggest chunk of the image is the SBCL itself (42 megabytes), the second largest chunk is the cl-gtk2 and just a tiny part is the app itself. I.e., should the app get bigger and more complex, the size of an image won't grow as much.
We can go even further and alleviate this problem using gzexe
:
gzexe hello-world-image
On my computer, the image size from 64Mb down to just 12Mb. This has the effect of increasing the load time (up to 1.4 seconds). 12Mb is acceptable even for downloading the app from the internet. Different Lisp implementations create images differently, and sizes vary from one implementation to another.
Note: saving images with having the cl-gtk2 loaded works only for SBCL for now, but it's just a matter of time until it will get implemented for other Lisp implementations.