aboutsummaryrefslogtreecommitdiffstats
path: root/Lisp/asdf/README
blob: 7243844330defc837f65bdb28df182abac0b8373 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
$Id: README 20 2005-12-27 15:21:23Z bjc $         -*- Text -*-


asdf: another system definition facility          
========================================

* Getting the latest version

0) Decide which version you want.  HEAD is the newest version and
usually OK, whereas RELEASE is for cautious people (e.g. who already
have systems using asdf that they don't want broken), a slightly older
version about which none of the HEAD users have complained.

1) Check it out from sourceforge cCLan CVS:

1a) cvs -d:pserver:anonymous@cvs.cclan.sourceforge.net:/cvsroot/cclan login
     (no password: just press Enter)
 
1a.1) cvs -z3 -d:pserver:anonymous@cvs.cclan.sourceforge.net:/cvsroot/cclan
         co -r RELEASE asdf

or for the bleeding edge, instead

1a.2) cvs -z3 -d:pserver:anonymous@cvs.cclan.sourceforge.net:/cvsroot/cclan
          co -A asdf

If you are tracking the bleeding edge, you may want to subscribe to
the cclan-commits mailing list (see
<URL:http://sourceforge.net/mail/?group_id=28536>) to receive commit
messages and diffs whenever changes are made.

For more CVS information, look at http://sourceforge.net/cvs/?group_id=28536


* Getting started

- The single file asdf.lisp is all you need to use asdf normally.  For
maximum convenience you want to have it loaded whenever you start your
Lisp implementation, by loading it from the startup script, or dumping
a custom core, or something.

- The variable asdf:*central-registry* is a list of system directory
  designators.  A system directory designator is a form which will be
  evaluated whenever a system is to be found, and must evaluate to a
  directory to look in.  For example, you might have

     (*default-pathname-defaults* "/home/me/cl/systems/"
      "/usr/share/common-lisp/systems/")

  (When we say "directory" here, we mean "designator for a pathname
  with a supplied DIRECTORY component")

  It is possible to customize the system definition file search.
  That's considered advanced use, and covered later: search forward
  for *system-definition-search-functions*

- To compile and load a system 'foo', you need to (1) ensure that
  foo.asd is in one of the directories in *central-registry* (a
  symlink to the real location of foo.asd is preferred), (2) execute
  ``(asdf:operate 'asdf:load-op 'foo)''

    $ cd /home/me/cl/systems/
    $ ln -s ~/src/foo/foo.asd .
    $ lisp
    * (asdf:operate 'asdf:load-op 'foo)

- To write your own system definitions, look at the test systems in
  test/ , and read the rest of this.  Ignore systems/ which is old
  and may go away when next I clean up

- Syntax is similar to mk-defsystem 3 for straightforward systems, you
  may only need to remove the :source-pathname option (and replace it
  with :pathname if the asd file is not in the same place as the
  system sources)

- Join cclan-list@lists.sf.net for discussion, bug reports, questions, etc

- cclan.asd and the source files listed therein contain useful extensions 
  for maintainers of systems in the cCLan.  If this isn't you, you
  don't need them - although you may want to look at them anyway

- For systems that do complicated things (e.g. compiling C files to
  load as foreign code), the packages in vn-cclan may provide some
  guidance.  db-sockets, for example, is known to do outlandish things
  with preprocessors

   http://ww.telent.net/cliki/vn-cclan 



* Concepts

This system definition utility talks in terms of 'components' and
'operations'.

Components form systems: a component represents a source file, or a
collection of components.  A system is therefore a component,
recursively formed of a tree of subcomponents.

Operations are instantiated then performed on the nodes of a tree to
do things like

 - compile all its files
 - load the files into a running lisp environment
 - copy its source files somewhere else

Operations can be invoked directly, or examined to see what their
effects would be without performing them.  There are a bunch of 
methods specialised on operation and component type which actually do
the grunt work.

asdf is extensible to new operations and to new component types.  This
allows the addition of behaviours: for example, a new component could
be added for Java JAR archives, and methods specialised on
compile-op added for it that would accomplish the relevant
actions.

* Inspiration

** mk-defsystem (defsystem-3.x)

We aim to solve basically the same problems as mk-defsystem does.
However, our architecture for extensibility better exploits CL
language features (and is documented), and we intend to be portable
rather than just widely-ported.  No slight on the mk-defsystem authors
and maintainers is intended here; that implementation has the
unenviable task of supporting non-ANSI implementations, which I
propose to ignore.

The surface defsystem syntax of asdf is more-or-less compatible with
mk-defsystem

The mk-defsystem code for topologically sorting a module's dependency
list was very useful.

** defsystem-4 proposal

Marco and Peter's proposal for defsystem 4 served as the driver for
many of the features in here.  Notable differences are

- we don't specify output files or output file extensions as part of
  the system

  If you want to find out what files an operation would create, ask
  the operation

- we don't deal with CL packages

  If you want to compile in a particular package, use an in-package
  form in that file (ilisp will like you more if you do this anyway)

- there is no proposal here that defsystem does version control.  

  A system has a given version which can be used to check
  dependencies, but that's all.

The defsystem 4 proposal tends to look more at the external features,
whereas this one centres on a protocol for system introspection.

** kmp's "The Description of Large Systems", MIT AI Memu 801

Available in updated-for-CL form on the web at 
http://world.std.com/~pitman/Papers/Large-Systems.html

In our implementation we borrow kmp's overall PROCESS-OPTIONS and
concept to deal with creating component trees from defsystem surface
syntax.  [ this is not true right now, though it used to be and
probably will be again soon ]


* The Objects

** component

*** Component Attributes

**** A name (required)

This is a string or a symbol.  If a symbol, its name is taken and
lowercased.  The name must be a suitable value for the :name initarg
to make-pathname in whatever filesystem the system is to be found.

The lower-casing-symbols behaviour is unconventional, but was selected
after some consideration.  Observations suggest that the type of
systems we want to support either have lowercase as customary case
(Unix, Mac, windows) or silently convert lowercase to uppercase
(lpns), so this makes more sense than attempting to use :case :common,
which is reported not to work on some implementations

**** a version identifier (optional)

This is used by the test-system-version operation (see later).

**** *features* required

Traditionally defsystem users have used reader conditionals to include
or exclude specific per-implementation files.  This means that any
single implementation cannot read the entire system, which becomes a
problem if it doesn't wish to compile it, but instead for example to
create an archive file containing all the sources, as it will omit to
process the system-dependent sources for other systems.

Each component in an asdf system may therefore specify features using
the same syntax as #+ does, and it will (somehow) be ignored for
certain operations unless the feature conditional matches

**** dependencies on its siblings  (optional but often necessary)

There is an excitingly complicated relationship between the initarg
and the method that you use to ask about dependencies

Dependencies are between (operation component) pairs.  In your
initargs, you can say

:in-order-to ((compile-op (load-op "a" "b") (compile-op "c"))
	      (load-op (load-op "foo")))

- before performing compile-op on this component, we must perform
load-op on "a" and "b", and compile-op on c, - before performing
load-op, we have to load "foo"

The syntax is approximately

(this-op {(other-op required-components)}+)

required-components := component-name
                     | (required-components required-components)

component-name := string
                | (:version string minimum-version-object)

[ This is on a par with what ACL defsystem does.  mk-defsystem is less
general: it has an implied dependency

  for all x, (load x) depends on (compile x)

and using a :depends-on argument to say that b depends on a _actually_
means that

  (compile b) depends on (load a) 

This is insufficient for e.g. the McCLIM system, which requires that
all the files are loaded before any of them can be compiled ]

In asdf, the dependency information for a given component and
operation can be queried using (component-depends-on operation
component), which returns a list

((load-op "a") (load-op "b") (compile-op "c") ...)

component-depends-on can be subclassed for more specific
component/operation types: these need to (call-next-method) and append
the answer to their dependency, unless they have a good reason for
completely overriding the default dependencies

(If it weren't for CLISP, we'd be using a LIST method combination to
do this transparently.  But, we need to support CLISP.  If you have
the time for some CLISP hacking, I'm sure they'd welcome your fixes)

**** a pathname

This is optional and if absent will be inferred from name, type (the
subclass of source-file), and the location of parent.

The rules for this inference are:

(for source-files)
- the host is taken from the parent
- pathname type is (source-file-type component system)
- the pathname case option is :local
- the pathname is merged against the parent

(for modules)
- the host is taken from the parent
- the name and type are NIL
- the directory is (:relative component-name)
- the pathname case option is :local
- the pathname is merged against the parent

Note that the DEFSYSTEM operator (used to create a "top-level" system)
does additional processing to set the filesystem location of the
top component in that system.  This is detailed elsewhere

The answer to the frequently asked question "how do I create a system 
definition where all the source files have a .cl extension" is thus

(defmethod source-file-type ((c cl-source-file) (s (eql (find-system 'my-sys))))
   "cl")

**** properties (optional)

Packaging systems often require information about files or systems
additional to that specified here.  Programs that create vendor
packages out of asdf systems therefore have to create "placeholder"
information to satisfy these systems.  Sometimes the creator of an
asdf system may know the additional information and wish to provide it
directly.

(component-property component property-name) and associated setf method 
will allow the programmatic update of this information.  Property
names are compared as if by EQL, so use symbols or keywords or something

** Subclasses of component

*** 'source-file'

A source file is any file that the system does not know how to
generate from other components of the system. 

(Note that this is not necessarily the same thing as "a file
containing data that is typically fed to a compiler".  If a file is
generated by some pre-processor stage (e.g. a ".h" file from ".h.in"
by autoconf) then it is not, by this definition, a source file.
Conversely, we might have a graphic file that cannot be automatically
regenerated, or a proprietary shared library that we received as a
binary: these do count as source files for our purposes.  All
suggestions for better terminology gratefully received)

Subclasses of source-file exist for various languages.  

*** 'module', a collection of sub-components

This has extra slots for

 :components - the components contained in this module

 :default-component-class - for child components which don't specify
   their class explicitly

 :if-component-dep-fails takes one of the values :fail, :try-next, :ignore 
   (default value is :fail).  The other values can be used for implementing
   conditional compilation based on implementation *features*, where
   it is not necessary for all files in a module to be compiled

The default operation knows how to traverse a module, so most
operations will not need to provide methods specialised on modules.

The module may be subclassed to represent components such as
foreign-language linked libraries or archive files.

*** system, subclasses module

A system is a module with a few extra attributes for documentation
purposes.  In behaviour, it's usually identical.

Users can create new classes for their systems: the default defsystem
macro takes a :classs keyword argument.


** operation

An operation is instantiated whenever the user asks that an operation
be performed, inspected, or etc.  The operation object contains
whatever state is relevant to this purpose (perhaps a list of visited
nodes, for example) but primarily is a nice thing to specialise
operation methods on and easier than having them all be EQL methods.

There are no differences between standard operations and user-defined
operations, except that the user is respectfully requested to keep his
(or more importantly, our) package namespace clean

*** invoking operations

(operate operation system &rest keywords-args)

keyword-args are passed to the make-instance call when creating the
operation: valid keywords depend on the initargs that the operation is
defined to accept.  Note that dependencies may cause the operation to
invoke other operations on the system or its components: the new
operation will be created with the same initargs as the original one.

oos is accepted as a synonym for operate

*** standard operations

**** feature-dependent-op

This is not intended to be instantiated directly, but other operations
may inherit from it.  An instance of feature-dependent-op will ignore
any components which have a `features' attribute, unless the feature
combination it designates is satisfied by *features*

See the earlier explanation about the component features attribute for
more information

**** compile-op &key proclamations

If proclamations are supplied, they will be proclaimed.  This is a
good place to specify optimization settings

When creating a new component, you should provide methods for this.  

If you invoke compile-op as a user, component dependencies often mean
you may get some parts of the system loaded.  This may not necessarily
be the whole thing, though; for your own sanity it is recommended that
you use load-op if you want to load a system.

**** load-op &key proclamations

The default methods for load-op compile files before loading them.
For parity, your own methods on new component types should probably do
so too

**** load-source-op

This method will load the source for the files in a module even if the
source files have been compiled. Systems sometimes have knotty
dependencies which require that sources are loaded before they can be
compiled.  This is how you do that.

If you are creating a component type, you need to implement this
operation - at least, where meaningful.

**** test-system-version &key minimum

Asks the system whether it satisfies a version requirement.

The default method accepts a string, which is expected to contain of a
number of integers separated by #\. characters.  The method is not
recursive.  The component satisfies the version dependency if it has
the same major number as required and each of its sub-versions is
greater than or equal to the sub-version number required.

(defun version-satisfies (x y)
  (labels ((bigger (x y)
	     (cond ((not y) t)
		   ((not x) nil)
		   ((> (car x) (car y)) t)
		   ((= (car x) (car y))
		    (bigger (cdr x) (cdr y))))))
    (and (= (car x) (car y))
	 (or (not (cdr y)) (bigger (cdr x) (cdr y))))))

If that doesn't work for your system, you can override it.  I hope
yoyu have as much fun writing the new method as #lisp did
reimplementing this one. 

*** Creating new operations

subclass operation, provide methods for source-file for 

- output-files
- perform
   The perform method must call output-files to find out where to
   put its files, because the user is allowed to override output-files
   for local policy
- explain
- operation-done-p, if you don't like the default one

* Writing system definitions

** System designators

System designators are strings or symbols and behave just like
any other component names (including case conversion)

** find-system

Given a system designator, find-system finds an actual system - either
in memory, or in a file on the disk.  It funcalls each element in the
*system-definition-search-functions* list, expecting a pathname to be
returned.

If a suitable file exists, it is loaded if

- there is no system of that name in memory, 
- the file's last-modified time exceeds the last-modified time of the
  system in memory

When system definitions are loaded from .asd files, a new scratch
package is created for them to load into, so that different systems do
not overwrite each others operations.  The user may also wish to (and
is recommended to) include defpackage and in-package forms in his
system definition files, however, so that they can be loaded manually
if need be.

For convenience in the normal case, and for backward compatibility
with the spirit of mk-defsystem, the default contents of
*system-definition-search-functions* is a function called
sysdef-central-registry-search.  This looks in each of the directories
given by evaluating members of *central-registry*, for a file whose
name is the name of the system and whose type is "asd".  The first
such file is returned, whether or not it turns out to actually define
the appropriate system



** Syntax

Systems can always be constructed programmatically by instantiating
components using make-instance.  For most purposes, however, it is
likely that people will want a static defystem form. 

asdf is based around the principle that components should not have to
know defsystem syntax.  That is, the initargs that a component accepts
are not necessarily related to the defsystem form which creates it.

A defsystem parser must implement a `defsystem' macro, which can
be named for compatibility with whatever other system definition
utility is being emulated.  It should instantiate components in
accordance with whatever language it accepts, and register the topmost
component using REGISTER-SYSTEM

*** Native syntax

The native syntax is inspired by mk-defsystem, to the extent that it
should be possible to take most straightforward mk- system definitions
and run them with only light editing.  For my convenience, this turns
out to be basically the same as the initargs to the various
components, with a few extensions for convenience
               
system-definition := ( defsystem system-designator {option}* )

option := :components component-list
        | :pathname pathname
        | :default-component-class
        | :perform method-form 
        | :explain method-form
	| :output-files  method-form
        | :operation-done-p method-form
        | :depends-on ( {simple-component-name}* ) 
	| :serial [ t | nil ]
        | :in-order-to ( {dependency}+ )

component-list := ( {component-def}* )
                
component-def  := simple-component-name
                | ( component-type name {option}* )

component-type := :module | :file | :system | other-component-type

dependency := (dependent-op {requirement}+)
requirement := (required-op {required-component}+)
             | (feature feature-name)
dependent-op := operation-name
required-op := operation-name | feature

For example

(defsystem "foo"
  :version "1.0"
  :components ((:module "foo" :components ((:file "bar") (:file"baz") 
                                           (:file "quux"))
	        :perform (compile-op :after (op c)
			  (do-something c))
		:explain (compile-op :after (op c)
			  (explain-something c)))
               (:file "blah")))


The method-form tokens need explaining: esentially, 

	        :perform (compile-op :after (op c)
			  (do-something c))
		:explain (compile-op :after (op c)
			  (explain-something c)))
has the effect of

(defmethod perform :after ((op compile-op) (c (eql ...)))
	   (do-something c))
(defmethod explain :after ((op compile-op) (c (eql ...)))
	   (explain-something c))

where ... is the component in question; note that although this also
supports :before methods, they may not do what you want them to - a
:before method on perform ((op compile-op) (c (eql ...)))  will run
after all the dependencies and sub-components have been processed, but
before the component in question has been compiled.

**** Serial dependencies

If the `:serial t' option is specified for a module, asdf will add
dependencies for each each child component, on all the children
textually preceding it.  This is done as if by :depends-on

:components ((:file "a") (:file "b") (:file "c"))
:serial t

is equivalent to
:components ((:file "a") 
	     (:file "b" :depends-on ("a"))
	     (:file "c" :depends-on ("a" "b")))



have all the 

**** Source location

The :pathname option is optional in all cases for native-syntax
systems, and in the usual case the user is recommended not to supply
it.  If it is not supplied for the top-level form, defsystem will set
it from

- The host/device/directory parts of *load-truename*, if it is bound
- *default-pathname-defaults*, otherwise

If a system is being redefined, the top-level pathname will be 

- changed, if explicitly supplied or obtained from *load-truename*
- changed if it had previously been set from *default-pathname-defaults*
- left as before, if it had previously been set from *load-truename*
  and *load-truename* is not now bound

These rules are designed so that (i) find-system will load a system
from disk and have its pathname default to the right place, (ii)
this pathname information will not be overwritten with
*default-pathname-defaults* (which could be somewhere else altogether)
if the user loads up the .asd file into his editor and
interactively re-evaluates that form

* Error handling

It is an error to define a system incorrectly: an implementation may
detect this and signal a generalised instance of
SYSTEM-DEFINITION-ERROR

Operations may go wrong (for example when source files contain
errors).  These are signalled using generalised instances of
OPERATION-ERROR

* Compilation error and warning handling

ASDF checks for warnings and errors when a file is compiled. The
variables *compile-file-warnings-behaviour* and
*compile-file-errors-behavior* controls the handling of any such
events. The valid values for these variables are :error, :warn, and
:ignore.

----------------------------------------------------------
                      TODO List
----------------------------------------------------------

* Outstanding spec questions, things to add

** packaging systems

*** manual page component?

** style guide for .asd files

You should either use keywords or be careful with the package that you
evaluate defsystem forms in.  Otherwise (defsystem partition ...)
being read in the cl-user package will intern a cl-user:partition
symbol, which will then collide with the partition:partition symbol.

Actually there's a hairier packages problem to think about too.
in-order-to is not a keyword: if you read defsystem forms in a package
that doesn't use ASDF, odd things might happen

** extending defsystem with new options

You might not want to write a whole parser, but just to add options to
the existing syntax.  Reinstate parse-option or something akin

** document all the error classes

** what to do with compile-file failure

Should check the primary return value from compile-file and see if
that gets us any closer to a sensible error handling strategy

** foreign files

lift unix-dso stuff from db-sockets

** Diagnostics

A "dry run" of an operation can be made with the following form:

(traverse (make-instance '<operation-name>)
          (find-system <system-name>)
          'explain)

This uses unexported symbols.  What would be a nice interface for this
functionality?

** patches

Sometimes one wants to 


* missing bits in implementation

** all of the above
** reuse the same scratch package whenever a system is reloaded from disk
** rules for system pathname defaulting are not yet implemented properly
** proclamations probably aren't
** when a system is reloaded with fewer components than it previously
   had, odd things happen

we should do something inventive when processing a defsystem form,
like take the list of kids and setf the slot to nil, then transfer
children from old to new list as they're found

**  traverse may become a normal function

If you're defining methods on traverse,  speak up.


** a lot of load-op methods can be rewritten to use input-files

so should be.


** (stuff that might happen later)

*** david lichteblau's patch for symlink resolution?

*** Propagation of the :force option.  ``I notice that

	(oos 'compile-op :araneida :force t)

also forces compilation of every other system the :araneida system
depends on.  This is rarely useful to me; usually, when I want to force
recompilation of something more than a single source file, I want to
recompile only one system.  So it would be more useful to have
make-sub-operation refuse to propagate ":force t" to other systems, and
propagate only something like ":force :recursively". ''

Ideally what we actually want is some kind of criterion that says
to which systems (and which operations) a :force switch will propagate.

The problem is perhaps that 'force' is a pretty meaningless concept.
How obvious is it that "load :force t" should force _compilation_?
But we don't really have the right dependency setup for the user to
compile :force t and expect it to work (files will not be loaded after
compilation, so the compile environment for subsequent files will be
emptier than it needs to be)

What does the user actually want to do when he forces?  Usually, for
me, update for use with a new version of the lisp compiler.  Perhaps
for recovery when he suspects that something has gone wrong.  Or else
when he's changed compilation options or configuration in some way
that's not reflected in the dependency graph.

Other possible interface: have a 'revert' function akin to 'make clean'

  (asdf:revert 'asdf:compile-op 'araneida) 

would delete any files produced by 'compile-op 'araneida.  Of course, it
wouldn't be able to do much about stuff in the image itself.

How would this work?

traverse

There's a difference between a module's dependencies (peers) and its
components (children).  Perhaps there's a similar difference in
operations?  For example, (load "use") depends-on (load "macros") is a
peer, whereas (load "use") depends-on (compile "use") is more of a
`subservient' relationship.