All Manuals > LispWorks® User Guide and Reference Manual > 20 Common Defsystem and ASDF

20.2 Defining a system

A system is defined with a defsystem form in an ordinary Lisp source file. This form must be loaded into the Lisp image in order to define the system in the environment. Once loaded, operations can be carried out on the system by invoking Lisp functions, or, more conveniently, by using the system browser.

For example, the expression:

CL-USER 5 > (compile-system 'debug-app :force t)

would compile every file in a system called debug-app.

Note: When defining a hierarchy of systems, the leaf systems must be defined first — that is, a system must be declared before any systems that include it.

By convention, system definitions are placed in a file called defsys.lisp which usually resides in the same directory as the members of the system.

The full syntax is given in defsystem. Below is a brief introduction.

20.2.1 DEFSYSTEM syntax

defsystem takes four arguments: name, options, members and rules.

name should be a string that names the system.

options is a list of keyword-value pairs specifying attributes of the system such as the default location of its member files or the default compiler optimize qualities in effect when compile-system is called.

members lists the members of the system which can be source files (of Common Lisp or foreign code) or other systems (that is, subsystems).

rules is a set of rules describing the requirements for compilation and loading of the system members and the order in which this should take place.

See the following sections for more information about these parameters.

20.2.2 DEFSYSTEM options

Options may be specified to defsystem which affect the behavior of the system as a whole. For example, :package specifies a default package into which files in the system are compiled and loaded if the file itself does not contain its own package declaration. The :default-pathname option tells the system tools where to find files which are not expressed as a full pathname.

20.2.3 DEFSYSTEM members

The :members keyword to defsystem is used to specify the members of a system. The argument given to :members is a list of strings. A system member is either a file or another system, identified by a name. If a full pathname is given then the function pathname-name is used to identify the name of the member. Thus, for example, the name of a member expressed as /u/dubya/foo.lisp is foo.

System members must have unique names, by a case-insensitive string comparison, so if a system has a member called "foo" then it cannot have another member (a file or a system) named "foo", "FOO" or foo.

The behavior of any member within a system can be constrained by supplying keyword arguments to the member itself. So, for example, specifying the :source-only keyword ensures that only the source file for that member is ever loaded.

20.2.4 DEFSYSTEM rules

Rules may be defined in a system which modify the default behavior of that system, ensuring, for instance, that certain files are always loaded or compiled before others.

Rules apply to files and subsystems alike as members of their parent system, but are not inherited by subsystems.

When you invoke an action such as compiling a system, the following happens by default:

This behavior can be modified by describing dependencies between the members using rules. These are specified using the :rules keyword to defsystem.

A rule has three components:

The target(s).

The action that is performed if the rule executes successfully.

This is an action-member description like :compile "foo". The member can be an actual member of the system or :all (meaning the rule should apply to each member of the system).

The actions that the target(s) are :caused-by.

The actions that cause the rule to execute successfully.

This is a list of action-member descriptions. The member of each of these descriptions should be either a real system member, or :previous, which means all members listed before the member of the target in the system description.

If any of these descriptions are already in the current plan (as a result of other rules executing successfully, or as a result of default system behavior), they trigger successful execution of this rule.

The actions that the target(s) :requires.

The actions that need to be performed before the rule can execute successfully.

This is a list of action-member descriptions that should be planned for before the action on the target(s). Again, each member should either be a real member of the system, or :previous.

The use of the keyword :previous means, for example, that you can specify that in order to compile a file in the system, all the members that come before it must be loaded.

When the action and member of a target are matched during the traversal of the list of members, the target is inserted into the plan if either of the following are true:

If the target is put into the plan then other targets are inserted beforehand if the action-member description of any :requires clause is not already in the plan.

20.2.5 Examples

Consider an example system, demo, defined as follows:

(defsystem demo (:package "CL-USER")
  :members ("parent"
  :rules ((:in-order-to :compile ("child1" "child2")
           (:caused-by (:compile "parent"))
           (:requires (:load "parent")))))

This system compiles and loads members into the CL-USER package if the members themselves do not specify packages. The system contains three members — parent, child1, and child2 — which may themselves be either files or other systems. There is only one explicit rule in the example. If parent needs to be compiled (for instance, if it has been changed), then this causes child1 and child2 to be compiled as well, irrespective of whether they have themselves changed. In order for them to be compiled, parent must first be loaded.

Implicitly, it is always the case that if any member changes, it needs to be compiled when you compile the system. The explicit rule above means that if the changed member happens to be parent, then every member gets compiled. If the changed member is not parent, then parent must at least be loaded before compiling takes place.

The next example shows a system consisting of three files:

(defsystem my-system 
  (:default-pathname "~/junk/")
   :members ("a" "b" "c")
   :rules ((:in-order-to :compile  ("c")
            (:requires (:load "a"))
            (:caused-by (:compile "b")))))

What plan is produced when all three files have already been compiled, but the file b.lisp has since been changed?

First, file a.lisp is considered. This file has already been compiled, so no instructions are added to the plan.

Second, file b.lisp is considered. Since this file has changed, the instruction compile b is added to the plan.

Finally file c.lisp is considered. Although this has already been compiled, the clause:

(:caused-by (:compile "b")) 

causes the instruction compile c to be added to the plan. The compilation of c.lisp also requires that a.lisp is loaded, so the instruction load a is added to the plan first. This gives us the following plan:

  1. Compile b.lisp.
  2. Load a.lisp.
  3. Compile c.lisp.

This last example shows how to make each fasl get loaded immediately after compiling it:

(defsystem my-system ()
  :members ("foo" "bar" "baz" "quux")
  :rules ((:in-order-to :compile :all
           (:requires (:load :previous)))))
(compile-system my-system :load t)

LispWorks® User Guide and Reference Manual - 01 Dec 2021 19:30:23