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.
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.
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.
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.
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.
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:
For example, in the case of compiling, a "compile this file" event is put into the plan if the source file is newer than the object file.
This behavior can be modified by describing dependencies between the members using
rules
. These are specified using the :rules
keyword to defsystem.
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:
:caused-by
clause is already in the plan, or
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.
Consider an example system, demo
, defined as follows:
(defsystem demo (:package "USER")
:members ("parent"
"child1"
"child2")
:rules ((:in-order-to :compile ("child1" "child2")
(:caused-by (:compile "parent"))
(:requires (:load "parent")))))
This system compiles and loads members into the 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:
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 - 20 Sep 2017