Next: , Previous: Defining systems with defsystem, Up: Top

7 The object model of ASDF

ASDF is designed in an object-oriented way from the ground up. Both a system's structure and the operations that can be performed on systems follow a extensible protocol.

This allows the addition of behaviours: for example, cffi adds support of special FFI description files to interface with C libraries and of wrapper files to embed C code in Lisp; abcl-jar supports creating Java JAR archives in ABCL; and poiu supports for compiling code in parallel using background processes.

This chapter deals with components and operations.

A component represents an individual source file or a group of source files, and the things that get transformed into. A system is a component at the top level of the component hierarchy. A source-file is a component representing a single source-file and the successive output files into which it is transformed. A module is an intermediate component itself grouping several other components, themselves source-files or further modules.

An Operation represents a transformation that can be performed on a component, turning them from source files to intermediate results to final outputs.

A pair of an operation and a component is called an action. An action represents a particular build step to be performed, after all its dependencies have been fulfilled. In the ASDF model, actions depend on other actions. The term action itself was used by Kent Pitman in his old article, but was only used by ASDF hackers starting with the ASDF 2; but the concept is ubiquitous since the very beginning of ASDF 1, though previously implicit.

Then, there are many functions available to users, extenders and implementers of ASDF to use, define or implement the activities that are part of building your software. Though they manipulate actions, most of these functions do not take as an argument a reified pair (a cons cell) of an operation and a component; instead, they usually take two separate arguments, which allows to take advantage of the power CLOS-style multiple dispatch for fun and profit.

There are many hooks in which to add functionality, by customizing the behavior of existing functions.

Last but not least is the notion of dependency between two actions. The structure of dependencies between actions is a directed dependency graph. ASDF is invoked by being told to operate with some operation on some toplevel system; it will then traverse the graph and build a plan that follows its structure. To be successfully buildable, this graph of actions but be acyclic. If, as a user, extender or implementer of ASDF, you fail to keep the dependency graph without cycles, ASDF will fail loudly as it eventually finds one. To clearly distinguish the direction of dependencies, ASDF 3 uses the words requiring and required as applied to an action depending on the other: the requiring action depends-on the completion of all required actions before it may itself be performed.

Using the defsystem syntax, users may easily express direct dependencies along the graph of the object hierarchy: between a component and its parent, its children, and its siblings. By defining custom CLOS methods, you can express more elaborate dependencies as you wish. Most common operations, such as load-op, compile-op or load-source-op are automatically propagate “downward” the component hierarchy and are “covariant” with it: to act the operation on the parent module, you must first act it on all the children components, with the action on the parent being parent of the action on each child. Other operations, such as prepare-op and prepare-source-op (introduced in ASDF 3) are automatically propagated “upward” the component hierarchy and are “contravariant” with it: to perform the operation of preparing for compilation of a child component, you must perform the operation of preparing for compilation of its parent component, and so on, ensuring that all the parent's dependencies are (compiled and) loaded before the child component may be compiled and loaded. Yet other operations, such as test-op or load-fasl-op remain at the system level, and are not propagated along the hierarchy, but instead do something global on the system.