Version | Date | Author | Comment |
---|---|---|---|
v0.1 |
2022-07-17 |
Pascal COMBIER |
Initial release |
v0.2 |
2022-07-23 |
Pascal COMBIER |
Add SLIME chapter |
v0.3 |
2022-07-31 |
Pascal COMBIER |
Add examples |
v0.4 |
2022-08-13 |
Pascal COMBIER |
Fix issues |
plain-common-lisp is a lightweight framework created to make it easier for software developers to develop and distribute Common Lisp’s applications on Microsoft Windows:
-
Download the latest release of plain-common-lisp
-
Extract the archive in your workspace
-
Done: you have a decent Quicklisp-enabled and SLIME-compatible Common Lisp’s distribution on Windows!
At this point, the Common Lisp’s REPL is ready, and the user can already work with all the third-party libraries available from Quicklisp! This makes Common Lisp a good candidate for writing small programs and utilities. Thousands of Common Lisp’s libraries are available, to give an example, one can install a HTTP library and start to use it immediately:
The changes are persistent: the installed libraries will be available after a restart.
plain-common-lisp is basically a ready-to-use distribution of SBCL and Quicklisp. The distribution is small and only contains a few files.
plain-common-lisp
│ plain-common-lisp.exe
│ plain-common-lisp-help.txt
│ README.md
├───applications
├───cache
├───configs
│ plain-common-lisp.cfg
├───sources
│ └───lisp
│ pcl-loader.lisp
└───third-party
├───binaries
│ │ sbcl.core
│ │ sbcl.exe
│ └───contrib (SBCL's fasl files)
└───quicklisp
quicklisp.lisp
To keep the plain-common-lisp archive small, Quicklisp is not included. For that reason, the first startup might be a little bit slow because plain-common-lisp will:
-
Download and install the last version of Quicklisp from the Internet
-
Compile the Lisp code and store the compilation results into the "cache" directory
The following executions should be much faster.
This chapter will describe how to use the plain-common-lisp project to develop and distribute Common Lisp applications. All the examples can be downloaded from the releases area of plain-common-lisp.
Let’s write a Hello-World application with plain-common-lisp. To do that, one just need to extract the last release of plain-common-lisp project:
The "applications" directory is initially empty:
One first need to create a directory "hello-world" to store the files for the application "hello-world".
Each application contains a directory named "systems". This directory must contain at least one ASDF system. Nothing specific to plain-common-lisp here, this structure is common to most Common Lisp’s projects.
The optional "third-party" directory is not used in this example. It can be used to store third-party systems and third-party binaries (i.e. DLL files).
The file "hello-world.asd" defines the way to compile the source code of the application. The format is documented in the ASDF project.
;;; +----------+-------------------------------------------------------+
;;; | Info | Value |
;;; +----------+-------------------------------------------------------+
;;; | Filename | hello-world.asd |
;;; | Project | plain-common-lisp-examples |
;;; +------------------------------------------------------------------+
(asdf:defsystem #:hello-world
:description "Hello world for plain-common-lisp"
:author "Pascal COMBIER"
:license "BSD"
:components
((:file "package")
(:file "hello-world" :depends-on ("package"))))
The "package.lisp" file describe the package "hello-world" which exports the "main" function:
;;; +----------+-------------------------------------------------------+
;;; | Info | Value |
;;; +----------+-------------------------------------------------------+
;;; | Filename | package.lisp |
;;; | Project | plain-common-lisp-examples |
;;; +----------+-------------------------------------------------------+
(defpackage #:hello-world
(:use
#:common-lisp)
(:export #:main))
The file "hello-world.lisp" implements the "main" function.
;;; +----------+-------------------------------------------------------+
;;; | Info | Value |
;;; +----------+-------------------------------------------------------+
;;; | Filename | hello-world.lisp |
;;; | Project | plain-common-lisp-examples |
;;; +----------+-------------------------------------------------------+
(in-package :hello-world)
;;--------------------------------------------------------------------;;
;; IMPLEMENTATION ;;
;;--------------------------------------------------------------------;;
(defun main ()
(format t "Hello World!~%"))
It’s trivial to test such application because all the applications in the "applications" directory are automatically registered to ASDF at plain-common-lisp’s startup:
To distribute this application, one way could be to distribute it with its source code. An easy approach would be to duplicate "plain-common-lisp.exe" into "hello-world.exe" and duplicate "configs/plain-common-lisp.cfg" into "configs/hello-world.cfg".
Note that "plain-common-lisp.exe" is actually a copy of the executable from the plain-starter project.
The last step would be to create an application starter file in the applications directory.
(asdf:load-system "hello-world")
(hello-world:main)
Executing "hello-world.exe" will have the behavior that everyone expects:
The final step before creating a ZIP file and distribute this application would be to delete the unnecessary files: "plain-common-lisp.exe", "configs/plain-common-lisp.cfg" and remove all the files from the cache directory.
A second way would be to distribute this application as a standalone binary file, without any source code attached. This can be achieved by using the save-lisp-and-die function from SBCL.
(sb-ext:save-lisp-and-die "hello-world-standalone.exe" :toplevel #'hello-world:main :executable t :compression t)
Note that the "compression" flag is not mandatory here. It’s a SBCL feature which is not always enabled on the official SBCL binaries for Windows. The SBCL binaries of plain-common-lisp’s always have this feature activated, allowing to trade a little bit of startup time to get a smaller binary size. Note that since SBCL 2.2.6, the zstd from Facebook is used for the compression. A compressed hello-world will typically take 12.5 MiB and the startup time be negligible.
That’s it! The application can be distributed to its users.
It is possible to change the icon present in the executable file without recompiling the program. The cost-free proprietary program Resource Hacker v4.5.30 has been reported working with plain-common-lisp’s executable files.
All the other examples can be downloaded from the releases area of plain-common-lisp. For each example, the program "make-standalone-executable.exe" will generate a standalone executable from the provided Lisp sources.
All the examples should be a little bit slow to start at the first execution. This is perfectly normal because plain-common-lisp will download and install Quicklisp from the internet and compile it. The "cache" directory will then be populated with the results of the compilation. This could take up to a couple of minutes on old systems. The following executions will be much faster. The executions from the standalone executables will be quite fast.
This example shows how to integrate plain-common-lisp with GNU Emacs and SLIME. "plain-common-lisp-swank.exe" will start a SWANK server so that SLIME could connect to it and interact with plain-common-lisp. More details are available in a dedicated chapter of this document.
This example shows how to write console applications with plain-common-lisp. Here "cat" refers to the cat command from Unix.
This example shows how to use the ltk library with plain-common-lisp. LTK will use the Tk binaries from Tcl/Tk and will require the program "wish.exe" to be shipped with the application. "wish.exe" is included with the example.
This chapter is based on a fresh installation of the vanilla GNU Emacs. The default package repository contains an old SLIME version which is not working properly. The third-party repository MELPA contains a good version. The first step is to add this MELPA repository to GNU Emacs.
Note that SLIME refers to the package for GNU Emacs and SWANK refers to the implementation of a debugging server embedded in the application.
Press Alt-x
and then enter the command customize-variable
.
Input package-archives
.
Click on INS
to insert a new repository:
-
Name: MELPA
Click on "STATE" and then "Save for Future Sessions".
Restart GNU Emacs. This is not techically required but slighly simplier to document.
Press Alt-x
and then enter the command list-packages
. Wait a few seconds for
the package list to be downloaded.
Find the MELPA version of "SLIME" and press i
the mark the software for installation.
Press x
to start the installation.
That’s done, SLIME is installed on GNU Emacs.
This chapter is based on a fresh installation of plain-common-lisp.
Install SWANK from Quicklisp with the command (ql:quickload "swank")
.
One can start a SWANK server with the function (swank:create-server)
which
will create a local server. By default, this server will listen on the port
4005. This function will need to be called each time the application is
executed.
Create a new Lisp file in the "applications" directory.
For example, one can write a hello-world function.
At this stage, let’s try to make Emacs connect to the plain-common-lisp
process. Press Alt-x
and type the command slime-connect
.
When prompted about which host to use, just validate: the default host
localhost
is perfectly fine.
When prompted about which port to use, just validate: the default port
4005
is perfectly fine.
That’s it, SLIME is started and connected to the plain-common-lisp process.
To compile the hello-world
function and send it to plain-common-lisp, it is
simply needed to type Ctrl-c Ctrl-c
. The result of the compilation will appear
in the terminal below the source code. One can directly test the hello-world
function by jumping in the REPL and typing the Common Lisp code (hello-world)
.
This is exactly why it is named interactive. The programmer write a function in its source code and test it immediately. If the function is working, the developer can save the file and then write a new function. The development of the program is done step-by-step in a incremental way.
In most of Common Lisp’s programs there are different packages. By default,
SLIME starts in the standard package common-lisp-user
also named
CL-USER
. All the functions will be created in this package. If one want to
switch to another package, he can:
-
Press
Alt-x
and then type the commandslime-repl-set-package
-
Use the shortcut
Ctrl-c
thenAlt-p
The package names can be automatically completed when pressing the TAB
key.
In the example below, we have created a package "hello" exporting the "main" function. Then we asked SLIME to jump inside this package. At this point, we implemented the "main" function and tested it.
A full example is available and can be downloaded from the releases area of plain-common-lisp. We can make the assumption that the SWANK server might not be needed when delivering the application to the users. So it could be reasonable to consider 2 environments:
-
Development environment, starting SWANK server automatically
-
Production environment, without any SWANK server
Creating a new environment simply means duplicating 2 files. Duplicate "plain-common-lisp.exe" into "plain-common-lisp-dev.exe". Duplicate "configs/plain-common-lisp.cfg" into "configs/plain-common-lisp-dev.cfg".
Then one simply need to write the "plain-common-lisp-dev" application startup file named "plain-common-lisp-dev.lisp".
(asdf:load-system "swank")
(swank:create-server)
When the program "plain-common-lisp-dev.exe" will be executed, it will try to load and execute the file "applications\plain-common-lisp-dev.lisp". This startup file will load SWANK and create a server.
After that, we can just run the application "plain-common-lisp-dev.exe" and connect with SLIME from GNU Emacs. The SWANK server is started automatically.
To explain how plain-common-lisp’s application, it’s convenient to describe how the "hello-world" example is started.
-
The user starts "hello-world.exe"
-
hello-world.exe will look for "config/hello-world.cfg", register the environment variable PCL_PROGNAME as "hello-world" and starts sbcl.exe
-
sbcl.exe will initialize plain-common-lisp with the file "sources/pcl-loader.lisp"
-
pcl-loader.lisp will start "applications\%PCL_PROGNAME%.lisp", in our case "applications\hello-world.lisp"
This way seems complex but has several advantages:
-
One plain-common-lisp directory can host several applications sharing a common source code.
-
All the applications use the same sbcl.exe, sbcl.core and contribs, making the system simple to maintain and update.
plain-common-lisp does not work completely with the vanilla SBCL, a few changes have been made on SBCL:
-
The additional hook sb-ext:*pre-foreign-init-hooks* has been added. It is called just before the initialization of the foreign module, allowing DLL files to be relocated at runtime, and therefore allowing plain-common-lisp’s applications to be moved accross the disk.
-
A manifest file has been added to the binary file, allowing plain-common-lisp’s GUIs to enable Windows visual styles.
-
The default icon of sbcl.exe has been replaced with plainstarter’s icon, to make it clear that the 2 binaries are different.
-
The compression option has been activated (it does not seem to be activated in all the builds for Windows).
-
Static linking has been activated to avoid the need for libzstd.dll.
This method is unfortunately not currently supported. The reason is technical, the SWANK package from Quicklisp implements its own FASL binaries relocation scheme. It does it in a way which is not compatible with plain-common-lisp.
The FASL files from plain-common-lisp and SWANK being located in different directories, plain-common-lisp startup meets an error when loading SWANK. If one successfuly modify SWANK so that he don’t implement any custom FASL redirection, this issue would probably be solved.
-
Quicklisp is the fantastic library manager for Common Lisp developped by Zach Beane. Note that Quicklisp is unaffiliated to plain-common-lisp’s project.
-
SLIME is a powerful mode for GNU Emacs allowing to write programs in an interactive and incremental way.
-
ASDF is the de-facto standard tool to build Common Lisp software. It has been maintained over 10 years and greatly documented by the outstanding François-René Rideau.