If you are a novice Lisp programmer and you are confused about the difference between systems and packages, I recommend you to read the Packages, systems, modules, libraries - WTF? before you continue.

Introduction

The most common way that people use packages nowadays is by adding a packages.lisp or package.lisp file. This file is usually the first file to be loaded and define the packages that the other files use. This approach has worked for many projects. However, when packages become larger, it requires discipline to manage the dependencies between the files.

An alternative approach to the definition of packages is named one package per file. As the name suggests, it consists in starting every file with a defpackage. Because dependencies between packages are explicit and every file has a unique package, the dependencies between the files can be inferred.

This style of programming was introduced a few years ago by faslpath and quick-build. But recently, the de facto standard Common Lisp build system ASDF3 added support for it with the asdf-package-system extension. As a consequence, now it is easier than ever to use it.

So, stay with me for the next minutes and you will learn how to use this new approach in your projects. I hope you find it useful.

How to use it

First of all, we have to enable the asdf-package-system extension on our system. We will work on a system named project as definition follows

(asdf:defsystem :project
  :name "My Project"
  :version "0.0.1"
  :class :package-inferred-system
  :defsystem-depends-on (:asdf-package-system)
  :depends-on (:project/addons))

This defines a correspondence between systems, packages and files. A system project/foo/bar refers the file foo/bar.lisp, which have to provide a package project/foo/bar, and whose used and imported packages refer to systems of the same name.

For example, as the system project depends on project/addons, the file addons.lisp must be loaded first. The content of addons.lisp starts with:

(defpackage :project/addons
  (:use :common-lisp :project/core)
  (:import-from :cl-ppcre))

(in-package :project/addons)

(cl-ppcre:scan-to-strings "h.*o" (hello))

Note that it uses the package project/core and "import" the cl-ppcre one. Therefore, ASDF will infer that it depends on both the systems cl-ppcre and project/core, so they must be loaded first. But remember, the system project/core refers to the file core.lisp:

(defpackage :project/core
  (:use :common-lisp)
  (:export #:hello))

(in-package :project/core)

(defun hello ()
  "Hello!")

And that is all. This file has no external dependencies. Then, if we try to load our system

(asdf:load-system "project")

the systems and files core.lisp, cl-ppcre and addons.lisp will be loaded in the proper order.

Integration with other systems

What if we use a package but the system that provides such a package has not the same (downcased) name? For example, the system closer-mop provides a package named c2cl. In order to let ASDF know how to find the system for a package, we can call the function register-system-packages with the name of the systems and the packages it provides as argument. In our case, we would include the following in our project.asd:

(register-system-packages :closer-mop '(:c2cl))

A Last Trick

Most of the time we want to export a single package with all the symbols from the "subpackages". This can be done very easily if you use the UIOP library shipped with ASDF. For example, let us define a file named all.lisp in our example like:

(uiop/package:define-package :project/all
  (:nickname :project)
  (:use-reexport :project/core :project/addons))

Now, the system project/all depends on both project/core and project/addons, and it will reexport the symbols into a :project package.

Futher information

If you want to know more about ASDF3 and package-system: