Skip to content

A trivial way to get a native Common Lisp environment on Windows

License

Notifications You must be signed in to change notification settings

pascalcombier/plain-common-lisp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

plain-common-lisp

Document versioning

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

Introduction

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:

  1. Download the latest release of plain-common-lisp

  2. Extract the archive in your workspace

  3. Done: you have a decent Quicklisp-enabled and SLIME-compatible Common Lisp’s distribution on Windows!

screenshot
screenshot

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:

screenshot

The changes are persistent: the installed libraries will be available after a restart.

screenshot

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.

screenshot
screenshot
screenshot
screenshot
screenshot
screenshot

Examples

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.

Console Hello World!

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:

screenshot

The "applications" directory is initially empty:

screenshot

One first need to create a directory "hello-world" to store the files for the application "hello-world".

screenshot
screenshot

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).

screenshot

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:

screenshot

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.

screenshot
screenshot

The last step would be to create an application starter file in the applications directory.

screenshot
hello-world.lisp
(asdf:load-system "hello-world")
(hello-world:main)

Executing "hello-world.exe" will have the behavior that everyone expects:

screenshot
screenshot

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.

screenshot

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.

screenshot
screenshot
screenshot

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.

screenshot
screenshot
screenshot

Other examples

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.

plain-common-lisp-swank

screenshot

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.

console-hello-world

screenshot

console-cat

screenshot

This example shows how to write console applications with plain-common-lisp. Here "cat" refers to the cat command from Unix.

gui-helloworld-win32

screenshot

This example shows how to use CFFI to access the Win32 API.

gui-helloworld-iup

screenshot

This example shows how to use the CL-IUP package with plain-common-lisp.

gui-paint-tk

screenshot

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.

gui-ftw-tetris

screenshot

This example shows how to use the ftw library with plain-common-lisp.

gui-opengl

screenshot

This example simply integrates an example from the "cl-glut-examples" available on Quicklisp.

SLIME configuration

Install SLIME on GNU Emacs

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.

screenshot

Press Alt-x and then enter the command customize-variable.

screenshot

Input package-archives.

screenshot

Click on INS to insert a new repository:

screenshot

Click on "STATE" and then "Save for Future Sessions".

screenshot

Restart GNU Emacs. This is not techically required but slighly simplier to document.

screenshot

Press Alt-x and then enter the command list-packages. Wait a few seconds for the package list to be downloaded.

screenshot

Find the MELPA version of "SLIME" and press i the mark the software for installation.

screenshot

Press x to start the installation.

screenshot

That’s done, SLIME is installed on GNU Emacs.

screenshot

Install SWANK in plain-common-lisp

This chapter is based on a fresh installation of plain-common-lisp.

screenshot

Install SWANK from Quicklisp with the command (ql:quickload "swank").

screenshot

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.

screenshot

Create a new Lisp file in the "applications" directory.

screenshot

For example, one can write a hello-world function.

screenshot

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.

screenshot

When prompted about which host to use, just validate: the default host localhost is perfectly fine.

screenshot

When prompted about which port to use, just validate: the default port 4005 is perfectly fine.

screenshot

That’s it, SLIME is started and connected to the plain-common-lisp process.

screenshot

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).

screenshot

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 command slime-repl-set-package

  • Use the shortcut Ctrl-c then Alt-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.

screenshot

Make the SLIME configuration persistent

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".

screenshot
screenshot

Then one simply need to write the "plain-common-lisp-dev" application startup file named "plain-common-lisp-dev.lisp".

screenshot
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.

screenshot

Technical information

Application startup

To explain how plain-common-lisp’s application, it’s convenient to describe how the "hello-world" example is started.

  1. The user starts "hello-world.exe"

  2. hello-world.exe will look for "config/hello-world.cfg", register the environment variable PCL_PROGNAME as "hello-world" and starts sbcl.exe

  3. sbcl.exe will initialize plain-common-lisp with the file "sources/pcl-loader.lisp"

  4. 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.

SBCL changes

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.

Known issues

SLIME does not work when spawning plain-common-lisp executables

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.

screenshot

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.

External references

  • 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.