ASDlite

ASDlite is a light-weight version of ASDF (Another System Definition Facility), an extensible build facility for Common Lisp. It supports basis ASDF functions and operation classes and can be used as a replacement in many cases.

Platforms

ASDlite was tested on the following Lisp implementations:

ASDlite design goals

  1. Small footprint.
  2. Ease of embedding into applications and systems not related to "compile-and-load Lisp files" tasks, for example, YstokHelp.
  3. ASDF compatibility for typical needs.
  4. Operation arguments specification in dependencies.

Operations in ASDlite

operation ::= keyword | operation-instance
operation-type ::= keyword | type-symbol
operation-designator ::= keyword | (keyword . plist) | type-symbol | operation-instance

Operations are passed to perform and other operation-specific methods. Operation designators can be used in the right-hand side of rules.

We encourage using simple keywords like :compile or :load. For these, ASDlite defines corresponding methods with eql specializers.

The plist allows you to pass key arguments to the operation methods. In the normal mode, ASDlite accepts only keyword-based forms.

If you feel these are not enough and need "full-fledged" ASDF operation classes, you can switch to the ASDF compatibility mode as follows:

  1. push :asdf-compat into *features* before compiling asdlite.lisp,
  2. refer to asdf:compile-op, asdf:load-op and the like,
  3. define your own subclasses of the operation class etc.

In the compatibility mode, ASDlite accepts all the above forms of operations and designators.

Dependencies in ASDlite

An action is a pair of an operation and a component. Some actions modify the file system, whereas other actions modify the current image, and this implies a difference in how to interpret timestamps.

Dependencies (rules) between actions are stored in each (target) component and represented by the two alists of target operations to other (dependee) actions.

There are two kinds of rules.

caused-by (named "in-order-to" in ASDF)
If any of dependee actions are already in the current plan (as its results have become out-of-date according to timestamp or as a result of other rules executing successfully), that triggers this rule, i.e. the target action is also placed into the plan.
requires (named "do-first" in ASDF)
These dependee actions have to be planned before the operation on the target component. But they do not trigger this rule per se, i.e. re-performing the target operation.

Syntax

rule ::= (target-op (dep-op {dependee}+)+)
target-op ::= operation-type
dep-op ::= operation-designator | :features
dependee ::= name-or-path | (name-or-path . plist)
name-or-path ::= component-name | vector | feature
plist ::= ([:features feature] [:version minimum-version] {property value}*)
feature ::= keyword | feature-expression

Example

(:component "A"
  :caused-by ((:compile (:compile "B" "C") (:load "D"))
              (:load (:load "B" "C")))
  :requires ((:compile (:load "B"))))

If B has changed, B.fasl needs to be recompiled. So the caused-by rule triggers recompiling of A.fasl irrespective of whether A has itself changed.

If A has changed, this neither imply compiling B nor C. But due to the requires rule loading B.fasl must be in the image precede compiling A.

ASDlite macroexpands the :depends-on option into a batch of caused-by rules similarly to what ASDF does (though this behavior is considered rather application-specific):

:depends-on dependee-list 
 =>
(:caused-by (:compile (:compile dependee-list))
            (:load (:load dependee-list))
            ...)

CAUTION: A component is only allowed to depend on its siblings, i.e. the components of the same module, no mater how we define dependencies:

Observation and rationale

  1. The ASDF built-in operation hierarchy is actually of two-level depth. The original ASDF code does not exploit operation inheritance much (though something can be found in asdf-ecl.lisp).
  2. The operation slots are rather useless:
    original-initargs
    Is only referred in print-object
    parent
    In principle, indicates the target operation that required this one.
    But due to reusing operation objects in make-sub-operation, this is not the case.
    Also used for marking along with other visiting slots during traverse but we follow another approach.
  3. Adding entirely new operations, i.e. on the first level, is fine. But there is comfortable way to refine existing operations: the easiest way is to add slots to base operation classes as only those properties do get passed into dependency operations.
    There is a more simple way pass arguments from operate to operation functions - by means of key arguments!
  4. The :do-first initarg is actually ignored by ASDF - its always set to
     ((compile-op (load-op ,@depends-on))))
  5. Avoid inline methods, which are rather inelegant in ASDF:
    - they rely on eval,
    - ASDF tries to remove a method from a generic function of a different name. Due to non-standard behavior of remove-method in LW 4.4, system redefinition intrusively signals about this.
  6. Referring to features in component definition is more useful than in dependency rules.
  7. Despite adherence to the object-oriented representation of operations, the source code exhibits "non-generic" approach to naming slot readers and accessors :-).
    For example:
     - component-parent vs. operation-parent
     - component-version vs. missing-version
     - module-components vs. circular-dependency-components

Platforms

The source code was tested on the following Lisp implementations:

Download and installation

Simply download the file asdlite.lisp, compile and load it.

Documentation

For general concepts and functions, follow the ASDF documentation.

Changes and additions