root/trunk/thirdparty/asdf/asdf.lisp

Revision 2236, 46.6 kB (checked in by hhubner, 1 year ago)

update asdf from cvs

Line 
1 ;;; This is asdf: Another System Definition Facility.  $Revision: 1.110 $
2 ;;;
3 ;;; Feedback, bug reports, and patches are all welcome: please mail to
4 ;;; <cclan-list@lists.sf.net>.  But note first that the canonical
5 ;;; source for asdf is presently the cCLan CVS repository at
6 ;;; <URL:http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/cclan/asdf/>
7 ;;;
8 ;;; If you obtained this copy from anywhere else, and you experience
9 ;;; trouble using it, or find bugs, you may want to check at the
10 ;;; location above for a more recent version (and for documentation
11 ;;; and test files, if your copy came without them) before reporting
12 ;;; bugs.  There are usually two "supported" revisions - the CVS HEAD
13 ;;; is the latest development version, whereas the revision tagged
14 ;;; RELEASE may be slightly older but is considered `stable'
15
16 ;;; Copyright (c) 2001-2007 Daniel Barlow and contributors
17 ;;;
18 ;;; Permission is hereby granted, free of charge, to any person obtaining
19 ;;; a copy of this software and associated documentation files (the
20 ;;; "Software"), to deal in the Software without restriction, including
21 ;;; without limitation the rights to use, copy, modify, merge, publish,
22 ;;; distribute, sublicense, and/or sell copies of the Software, and to
23 ;;; permit persons to whom the Software is furnished to do so, subject to
24 ;;; the following conditions:
25 ;;;
26 ;;; The above copyright notice and this permission notice shall be
27 ;;; included in all copies or substantial portions of the Software.
28 ;;;
29 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 ;;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 ;;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 ;;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36
37 ;;; the problem with writing a defsystem replacement is bootstrapping:
38 ;;; we can't use defsystem to compile it.  Hence, all in one file
39
40 (defpackage #:asdf
41   (:export #:defsystem #:oos #:operate #:find-system #:run-shell-command
42            #:system-definition-pathname #:find-component ; miscellaneous
43            #:hyperdocumentation #:hyperdoc
44            
45            #:compile-op #:load-op #:load-source-op #:test-system-version
46            #:test-op
47            #:operation                  ; operations
48            #:feature                    ; sort-of operation
49            #:version                    ; metaphorically sort-of an operation
50            
51            #:input-files #:output-files #:perform       ; operation methods
52            #:operation-done-p #:explain
53            
54            #:component #:source-file
55            #:c-source-file #:cl-source-file #:java-source-file
56            #:static-file
57            #:doc-file
58            #:html-file
59            #:text-file
60            #:source-file-type
61            #:module                     ; components
62            #:system
63            #:unix-dso
64            
65            #:module-components          ; component accessors
66            #:component-pathname
67            #:component-relative-pathname
68            #:component-name
69            #:component-version
70            #:component-parent
71            #:component-property
72            #:component-system
73            
74            #:component-depends-on
75
76            #:system-description
77            #:system-long-description
78            #:system-author
79            #:system-maintainer
80            #:system-license
81            #:system-licence
82            #:system-source-file
83            #:system-relative-pathname
84
85            #:operation-on-warnings
86            #:operation-on-failure
87            
88            ;#:*component-parent-pathname*
89            #:*system-definition-search-functions*
90            #:*central-registry*         ; variables
91            #:*compile-file-warnings-behaviour*
92            #:*compile-file-failure-behaviour*
93            #:*asdf-revision*
94            
95            #:operation-error #:compile-failed #:compile-warned #:compile-error
96            #:error-component #:error-operation
97            #:system-definition-error
98            #:missing-component
99            #:missing-dependency
100            #:circular-dependency        ; errors
101            #:duplicate-names
102            
103            #:retry
104            #:accept                     ; restarts
105            
106            #:preference-file-for-system/operation
107            #:load-preferences
108            )
109   (:use :cl))
110
111
112 #+nil
113 (error "The author of this file habitually uses #+nil to comment out forms.  But don't worry, it was unlikely to work in the New Implementation of Lisp anyway")
114
115
116 (in-package #:asdf)
117
118 (defvar *asdf-revision* (let* ((v "$Revision: 1.110 $")
119                                (colon (or (position #\: v) -1))
120                                (dot (position #\. v)))
121                           (and v colon dot
122                                (list (parse-integer v :start (1+ colon)
123                                                     :junk-allowed t)
124                                      (parse-integer v :start (1+ dot)
125                                                     :junk-allowed t)))))
126
127 (defvar *compile-file-warnings-behaviour* :warn)
128
129 (defvar *compile-file-failure-behaviour* #+sbcl :error #-sbcl :warn)
130
131 (defvar *verbose-out* nil)
132
133 (defparameter +asdf-methods+
134   '(perform explain output-files operation-done-p))
135
136 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
137 ;; utility stuff
138
139 (defmacro aif (test then &optional else)
140   `(let ((it ,test)) (if it ,then ,else)))
141
142 (defun pathname-sans-name+type (pathname)
143   "Returns a new pathname with same HOST, DEVICE, DIRECTORY as PATHNAME,
144 and NIL NAME and TYPE components"
145   (make-pathname :name nil :type nil :defaults pathname))
146
147 (define-modify-macro appendf (&rest args)
148                      append "Append onto list")
149
150 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
151 ;; classes, condiitons
152
153 (define-condition system-definition-error (error) ()
154   ;; [this use of :report should be redundant, but unfortunately it's not.
155   ;; cmucl's lisp::output-instance prefers the kernel:slot-class-print-function
156   ;; over print-object; this is always conditions::%print-condition for
157   ;; condition objects, which in turn does inheritance of :report options at
158   ;; run-time.  fortunately, inheritance means we only need this kludge here in
159   ;; order to fix all conditions that build on it.  -- rgr, 28-Jul-02.]
160   #+cmu (:report print-object))
161
162 (define-condition formatted-system-definition-error (system-definition-error)
163   ((format-control :initarg :format-control :reader format-control)
164    (format-arguments :initarg :format-arguments :reader format-arguments))
165   (:report (lambda (c s)
166              (apply #'format s (format-control c) (format-arguments c)))))
167
168 (define-condition circular-dependency (system-definition-error)
169   ((components :initarg :components :reader circular-dependency-components)))
170
171 (define-condition duplicate-names (system-definition-error)
172   ((name :initarg :name :reader duplicate-names-name)))
173
174 (define-condition missing-component (system-definition-error)
175   ((requires :initform "(unnamed)" :reader missing-requires :initarg :requires)
176    (version :initform nil :reader missing-version :initarg :version)
177    (parent :initform nil :reader missing-parent :initarg :parent)))
178
179 (define-condition missing-dependency (missing-component)
180   ((required-by :initarg :required-by :reader missing-required-by)))
181
182 (define-condition operation-error (error)
183   ((component :reader error-component :initarg :component)
184    (operation :reader error-operation :initarg :operation))
185   (:report (lambda (c s)
186              (format s "~@<erred while invoking ~A on ~A~@:>"
187                      (error-operation c) (error-component c)))))
188 (define-condition compile-error (operation-error) ())
189 (define-condition compile-failed (compile-error) ())
190 (define-condition compile-warned (compile-error) ())
191
192 (defclass component ()
193   ((name :accessor component-name :initarg :name :documentation
194          "Component name: designator for a string composed of portable pathname characters")
195    (version :accessor component-version :initarg :version)
196    (in-order-to :initform nil :initarg :in-order-to)
197    ;;; XXX crap name
198    (do-first :initform nil :initarg :do-first)
199    ;; methods defined using the "inline" style inside a defsystem form:
200    ;; need to store them somewhere so we can delete them when the system
201    ;; is re-evaluated
202    (inline-methods :accessor component-inline-methods :initform nil)
203    (parent :initarg :parent :initform nil :reader component-parent)
204    ;; no direct accessor for pathname, we do this as a method to allow
205    ;; it to default in funky ways if not supplied
206    (relative-pathname :initarg :pathname)
207    (operation-times :initform (make-hash-table )
208                     :accessor component-operation-times)
209    ;; XXX we should provide some atomic interface for updating the
210    ;; component properties
211    (properties :accessor component-properties :initarg :properties
212                :initform nil)))
213
214 ;;;; methods: conditions
215
216 (defmethod print-object ((c missing-dependency) s)
217   (format s "~@<~A, required by ~A~@:>"
218           (call-next-method c nil) (missing-required-by c)))
219
220 (defun sysdef-error (format &rest arguments)
221   (error 'formatted-system-definition-error :format-control format :format-arguments arguments))
222
223 ;;;; methods: components
224
225 (defmethod print-object ((c missing-component) s)
226   (format s "~@<component ~S not found~
227              ~@[ or does not match version ~A~]~
228              ~@[ in ~A~]~@:>"
229           (missing-requires c)
230           (missing-version c)
231           (when (missing-parent c)
232             (component-name (missing-parent c)))))
233
234 (defgeneric component-system (component)
235   (:documentation "Find the top-level system containing COMPONENT"))
236  
237 (defmethod component-system ((component component))
238   (aif (component-parent component)
239        (component-system it)
240        component))
241
242 (defmethod print-object ((c component) stream)
243   (print-unreadable-object (c stream :type t :identity t)
244     (ignore-errors
245       (prin1 (component-name c) stream))))
246
247 (defclass module (component)
248   ((components :initform nil :accessor module-components :initarg :components)
249    ;; what to do if we can't satisfy a dependency of one of this module's
250    ;; components.  This allows a limited form of conditional processing
251    (if-component-dep-fails :initform :fail
252                            :accessor module-if-component-dep-fails
253                            :initarg :if-component-dep-fails)
254    (default-component-class :accessor module-default-component-class
255      :initform 'cl-source-file :initarg :default-component-class)))
256
257 (defgeneric component-pathname (component)
258   (:documentation "Extracts the pathname applicable for a particular component."))
259
260 (defun component-parent-pathname (component)
261   (aif (component-parent component)
262        (component-pathname it)
263        *default-pathname-defaults*))
264
265 (defgeneric component-relative-pathname (component)
266   (:documentation "Extracts the relative pathname applicable for a particular component."))
267    
268 (defmethod component-relative-pathname ((component module))
269   (or (slot-value component 'relative-pathname)
270       (make-pathname
271        :directory `(:relative ,(component-name component))
272        :host (pathname-host (component-parent-pathname component)))))
273
274 (defmethod component-pathname ((component component))
275   (let ((*default-pathname-defaults* (component-parent-pathname component)))
276     (merge-pathnames (component-relative-pathname component))))
277
278 (defgeneric component-property (component property))
279
280 (defmethod component-property ((c component) property)
281   (cdr (assoc property (slot-value c 'properties) :test #'equal)))
282
283 (defgeneric (setf component-property) (new-value component property))
284
285 (defmethod (setf component-property) (new-value (c component) property)
286   (let ((a (assoc property (slot-value c 'properties) :test #'equal)))
287     (if a
288         (setf (cdr a) new-value)
289         (setf (slot-value c 'properties)
290               (acons property new-value (slot-value c 'properties))))))
291
292 (defclass system (module)
293   ((description :accessor system-description :initarg :description)
294    (long-description
295     :accessor system-long-description :initarg :long-description)
296    (author :accessor system-author :initarg :author)
297    (maintainer :accessor system-maintainer :initarg :maintainer)
298    (licence :accessor system-licence :initarg :licence
299             :accessor system-license :initarg :license)))
300
301 ;;; version-satisfies
302
303 ;;; with apologies to christophe rhodes ...
304 (defun split (string &optional max (ws '(#\Space #\Tab)))
305   (flet ((is-ws (char) (find char ws)))
306     (nreverse
307      (let ((list nil) (start 0) (words 0) end)
308        (loop
309         (when (and max (>= words (1- max)))
310           (return (cons (subseq string start) list)))
311         (setf end (position-if #'is-ws string :start start))
312         (push (subseq string start end) list)
313         (incf words)
314         (unless end (return list))
315         (setf start (1+ end)))))))
316
317 (defgeneric version-satisfies (component version))
318
319 (defmethod version-satisfies ((c component) version)
320   (unless (and version (slot-boundp c 'version))
321     (return-from version-satisfies t))
322   (let ((x (mapcar #'parse-integer
323                    (split (component-version c) nil '(#\.))))
324         (y (mapcar #'parse-integer
325                    (split version nil '(#\.)))))
326     (labels ((bigger (x y)
327                (cond ((not y) t)
328                      ((not x) nil)
329                      ((> (car x) (car y)) t)
330                      ((= (car x) (car y))
331                       (bigger (cdr x) (cdr y))))))
332       (and (= (car x) (car y))
333            (or (not (cdr y)) (bigger (cdr x) (cdr y)))))))
334
335 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
336 ;;; finding systems
337
338 (defvar *defined-systems* (make-hash-table :test 'equal))
339 (defun coerce-name (name)
340    (typecase name
341      (component (component-name name))
342      (symbol (string-downcase (symbol-name name)))
343      (string name)
344      (t (sysdef-error "~@<invalid component designator ~A~@:>" name))))
345
346 ;;; for the sake of keeping things reasonably neat, we adopt a
347 ;;; convention that functions in this list are prefixed SYSDEF-
348
349 (defvar *system-definition-search-functions*
350   '(sysdef-central-registry-search))
351
352 (defun system-definition-pathname (system)
353   (some (lambda (x) (funcall x system))
354         *system-definition-search-functions*))
355        
356 (defvar *central-registry*
357   '(*default-pathname-defaults*
358     #+nil "/home/dan/src/sourceforge/cclan/asdf/systems/"
359     #+nil "telent:asdf;systems;"))
360
361 (defun sysdef-central-registry-search (system)
362   (let ((name (coerce-name system)))
363     (block nil
364       (dolist (dir *central-registry*)
365         (let* ((defaults (eval dir))
366                (file (and defaults
367                           (make-pathname
368                            :defaults defaults :version :newest
369                            :name name :type "asd" :case :local))))
370           (if (and file (probe-file file))
371               (return file)))))))
372
373 (defun make-temporary-package ()
374   (flet ((try (counter)
375            (ignore-errors
376                    (make-package (format nil "ASDF~D" counter)
377                                  :use '(:cl :asdf)))))
378     (do* ((counter 0 (+ counter 1))
379           (package (try counter) (try counter)))
380          (package package))))
381
382 (defun find-system (name &optional (error-p t))
383   (let* ((name (coerce-name name))
384          (in-memory (gethash name *defined-systems*))
385          (on-disk (system-definition-pathname name)))   
386     (when (and on-disk
387                (or (not in-memory)
388                    (< (car in-memory) (file-write-date on-disk))))
389       (let ((package (make-temporary-package)))
390         (unwind-protect
391              (let ((*package* package))
392                (format
393                 *verbose-out*
394                 "~&~@<; ~@;loading system definition from ~A into ~A~@:>~%"
395                 ;; FIXME: This wants to be (ENOUGH-NAMESTRING
396                 ;; ON-DISK), but CMUCL barfs on that.
397                 on-disk
398                 *package*)
399                (load on-disk))
400           (delete-package package))))
401     (let ((in-memory (gethash name *defined-systems*)))
402       (if in-memory
403           (progn (if on-disk (setf (car in-memory) (file-write-date on-disk)))
404                  (cdr in-memory))
405           (if error-p (error 'missing-component :requires name))))))
406
407 (defun register-system (name system)
408   (format *verbose-out* "~&~@<; ~@;registering ~A as ~A~@:>~%" system name)
409   (setf (gethash (coerce-name  name) *defined-systems*)
410         (cons (get-universal-time) system)))
411
412 (defun system-registered-p (name)
413   (gethash (coerce-name name) *defined-systems*))
414
415 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
416 ;;; finding components
417
418 (defgeneric find-component (module name &optional version)
419   (:documentation "Finds the component with name NAME present in the
420 MODULE module; if MODULE is nil, then the component is assumed to be a
421 system."))
422
423 (defmethod find-component ((module module) name &optional version)
424   (if (slot-boundp module 'components)
425       (let ((m (find name (module-components module)
426                      :test #'equal :key #'component-name)))
427         (if (and m (version-satisfies m version)) m))))
428            
429
430 ;;; a component with no parent is a system
431 (defmethod find-component ((module (eql nil)) name &optional version)
432   (let ((m (find-system name nil)))
433     (if (and m (version-satisfies m version)) m)))
434
435 ;;; component subclasses
436
437 (defclass source-file (component) ())
438
439 (defclass cl-source-file (source-file) ())
440 (defclass c-source-file (source-file) ())
441 (defclass java-source-file (source-file) ())
442 (defclass static-file (source-file) ())
443 (defclass doc-file (static-file) ())
444 (defclass html-file (doc-file) ())
445
446 (defgeneric source-file-type (component system))
447 (defmethod source-file-type ((c cl-source-file) (s module)) "lisp")
448 (defmethod source-file-type ((c c-source-file) (s module)) "c")
449 (defmethod source-file-type ((c java-source-file) (s module)) "java")
450 (defmethod source-file-type ((c html-file) (s module)) "html")
451 (defmethod source-file-type ((c static-file) (s module)) nil)
452
453 (defmethod component-relative-pathname ((component source-file))
454   (let ((relative-pathname (slot-value component 'relative-pathname)))
455     (if relative-pathname
456         (merge-pathnames
457          relative-pathname
458          (make-pathname
459           :type (source-file-type component (component-system component))))
460         (let* ((*default-pathname-defaults*
461                 (component-parent-pathname component))
462                (name-type
463                 (make-pathname
464                  :name (component-name component)
465                  :type (source-file-type component
466                                          (component-system component)))))
467           name-type))))
468
469 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
470 ;;; operations
471
472 ;;; one of these is instantiated whenever (operate ) is called
473
474 (defclass operation ()
475   ((forced :initform nil :initarg :force :accessor operation-forced)
476    (original-initargs :initform nil :initarg :original-initargs
477                       :accessor operation-original-initargs)
478    (visited-nodes :initform nil :accessor operation-visited-nodes)
479    (visiting-nodes :initform nil :accessor operation-visiting-nodes)
480    (parent :initform nil :initarg :parent :accessor operation-parent)))
481
482 (defmethod print-object ((o operation) stream)
483   (print-unreadable-object (o stream :type t :identity t)
484     (ignore-errors
485       (prin1 (operation-original-initargs o) stream))))
486
487 (defmethod shared-initialize :after ((operation operation) slot-names
488                                      &key force
489                                      &allow-other-keys)
490   (declare (ignore slot-names force))
491   ;; empty method to disable initarg validity checking
492   )
493
494 (defgeneric perform (operation component))
495 (defgeneric operation-done-p (operation component))
496 (defgeneric explain (operation component))
497 (defgeneric output-files (operation component))
498 (defgeneric input-files (operation component))
499
500 (defun node-for (o c)
501   (cons (class-name (class-of o)) c))
502
503 (defgeneric operation-ancestor (operation)
504   (:documentation   "Recursively chase the operation's parent pointer until we get to the head of the tree"))
505
506 (defmethod operation-ancestor ((operation operation))
507   (aif (operation-parent operation)
508        (operation-ancestor it)
509        operation))
510
511
512 (defun make-sub-operation (c o dep-c dep-o)
513   (let* ((args (copy-list (operation-original-initargs o)))
514          (force-p (getf args :force)))
515     ;; note explicit comparison with T: any other non-NIL force value
516     ;; (e.g. :recursive) will pass through
517     (cond ((and (null (component-parent c))
518                 (null (component-parent dep-c))
519                 (not (eql c dep-c)))
520            (when (eql force-p t)
521              (setf (getf args :force) nil))
522            (apply #'make-instance dep-o
523                   :parent o
524                   :original-initargs args args))
525           ((subtypep (type-of o) dep-o)
526            o)
527           (t
528            (apply #'make-instance dep-o
529                   :parent o :original-initargs args args)))))
530
531
532 (defgeneric visit-component (operation component data))
533
534 (defmethod visit-component ((o operation) (c component) data)
535   (unless (component-visited-p o c)
536     (push (cons (node-for o c) data)
537           (operation-visited-nodes (operation-ancestor o)))))
538
539 (defgeneric component-visited-p (operation component))
540
541 (defmethod component-visited-p ((o operation) (c component))
542   (assoc (node-for o c)
543          (operation-visited-nodes (operation-ancestor o))
544          :test 'equal))
545
546 (defgeneric (setf visiting-component) (new-value operation component))
547
548 (defmethod (setf visiting-component) (new-value operation component)
549   ;; MCL complains about unused lexical variables
550   (declare (ignorable new-value operation component)))
551
552 (defmethod (setf visiting-component) (new-value (o operation) (c component))
553   (let ((node (node-for o c))
554         (a (operation-ancestor o)))
555     (if new-value
556         (pushnew node (operation-visiting-nodes a) :test 'equal)
557         (setf (operation-visiting-nodes a)
558               (remove node  (operation-visiting-nodes a) :test 'equal)))))
559
560 (defgeneric component-visiting-p (operation component))
561
562 (defmethod component-visiting-p ((o operation) (c component))
563   (let ((node (cons o c)))
564     (member node (operation-visiting-nodes (operation-ancestor o))
565             :test 'equal)))
566
567 (defgeneric component-depends-on (operation component)
568   (:documentation
569    "Returns a list of dependencies needed by the component to perform
570     the operation.  A dependency has one of the following forms:
571
572       (<operation> <component>*), where <operation> is a class
573         designator and each <component> is a component
574         designator, which means that the component depends on
575         <operation> having been performed on each <component>; or
576
577       (FEATURE <feature>), which means that the component depends
578         on <feature>'s presence in *FEATURES*.
579
580     Methods specialized on subclasses of existing component types
581     should usually append the results of CALL-NEXT-METHOD to the
582     list."))
583
584 (defmethod component-depends-on ((op-spec symbol) (c component))
585   (component-depends-on (make-instance op-spec) c))
586
587 (defmethod component-depends-on ((o operation) (c component))
588   (cdr (assoc (class-name (class-of o))
589               (slot-value c 'in-order-to))))
590
591 (defgeneric component-self-dependencies (operation component))
592
593 (defmethod component-self-dependencies ((o operation) (c component))
594   (let ((all-deps (component-depends-on o c)))
595     (remove-if-not (lambda (x)
596                      (member (component-name c) (cdr x) :test #'string=))
597                    all-deps)))
598    
599 (defmethod input-files ((operation operation) (c component))
600   (let ((parent (component-parent c))
601         (self-deps (component-self-dependencies operation c)))
602     (if self-deps
603         (mapcan (lambda (dep)
604                   (destructuring-bind (op name) dep
605                     (output-files (make-instance op)
606                                   (find-component parent name))))
607                 self-deps)
608         ;; no previous operations needed?  I guess we work with the
609         ;; original source file, then
610         (list (component-pathname c)))))
611
612 (defmethod input-files ((operation operation) (c module)) nil)
613
614 (defmethod operation-done-p ((o operation) (c component))
615   (flet ((fwd-or-return-t (file)
616            ;; if FILE-WRITE-DATE returns NIL, it's possible that the
617            ;; user or some other agent has deleted an input file.  If
618            ;; that's the case, well, that's not good, but as long as
619            ;; the operation is otherwise considered to be done we
620            ;; could continue and survive.
621            (let ((date (file-write-date file)))
622              (cond
623                (date)
624                (t
625                 (warn "~@<Missing FILE-WRITE-DATE for ~S: treating ~
626                        operation ~S on component ~S as done.~@:>"
627                       file o c)
628                 (return-from operation-done-p t))))))
629     (let ((out-files (output-files o c))
630           (in-files (input-files o c)))
631       (cond ((and (not in-files) (not out-files))
632              ;; arbitrary decision: an operation that uses nothing to
633              ;; produce nothing probably isn't doing much
634              t)
635             ((not out-files)
636              (let ((op-done
637                     (gethash (type-of o)
638                              (component-operation-times c))))
639                (and op-done
640                     (>= op-done
641                         (apply #'max
642                                (mapcar #'fwd-or-return-t in-files))))))
643             ((not in-files) nil)
644             (t
645              (and
646               (every #'probe-file out-files)
647               (> (apply #'min (mapcar #'file-write-date out-files))
648                  (apply #'max (mapcar #'fwd-or-return-t in-files)))))))))
649
650 ;;; So you look at this code and think "why isn't it a bunch of
651 ;;; methods".  And the answer is, because standard method combination
652 ;;; runs :before methods most->least-specific, which is back to front
653 ;;; for our purposes.  And CLISP doesn't have non-standard method
654 ;;; combinations, so let's keep it simple and aspire to portability
655
656 (defgeneric traverse (operation component))
657 (defmethod traverse ((operation operation) (c component))
658   (let ((forced nil))
659     (labels ((do-one-dep (required-op required-c required-v)
660                (let* ((dep-c (or (find-component
661                                   (component-parent c)
662                                   ;; XXX tacky.  really we should build the
663                                   ;; in-order-to slot with canonicalized
664                                   ;; names instead of coercing this late
665                                   (coerce-name required-c) required-v)
666                                  (error 'missing-dependency :required-by c
667                                         :version required-v
668                                         :requires required-c)))
669                       (op (make-sub-operation c operation dep-c required-op)))
670                  (traverse op dep-c)))             
671              (do-dep (op dep)
672                (cond ((eq op 'feature)
673                       (or (member (car dep) *features*)
674                           (error 'missing-dependency :required-by c
675                                  :requires (car dep) :version nil)))
676                      (t
677                       (dolist (d dep)
678                         (cond ((consp d)
679                                (assert (string-equal
680                                         (symbol-name (first d))
681                                         "VERSION"))
682                                (appendf forced
683                                         (do-one-dep op (second d) (third d))))
684                               (t
685                                (appendf forced (do-one-dep op d nil)))))))))
686       (aif (component-visited-p operation c)
687            (return-from traverse
688              (if (cdr it) (list (cons 'pruned-op c)) nil)))
689       ;; dependencies
690       (if (component-visiting-p operation c)
691           (error 'circular-dependency :components (list c)))
692       (setf (visiting-component operation c) t)
693       (loop for (required-op . deps) in (component-depends-on operation c)
694             do (do-dep required-op deps))
695       ;; constituent bits
696       (let ((module-ops
697              (when (typep c 'module)
698                (let ((at-least-one nil)
699                      (forced nil)
700                      (error nil))
701                  (loop for kid in (module-components c)
702                        do (handler-case
703                               (appendf forced (traverse operation kid ))
704                             (missing-dependency (condition)
705                               (if (eq (module-if-component-dep-fails c) :fail)
706                                   (error condition))
707                               (setf error condition))
708                             (:no-error (c)
709                               (declare (ignore c))
710                               (setf at-least-one t))))
711                  (when (and (eq (module-if-component-dep-fails c) :try-next)
712                             (not at-least-one))
713                    (error error))
714                  forced))))
715         ;; now the thing itself
716         (when (or forced module-ops
717                   (not (operation-done-p operation c))
718                   (let ((f (operation-forced (operation-ancestor operation))))
719                     (and f (or (not (consp f))
720                                (member (component-name
721                                         (operation-ancestor operation))
722                                        (mapcar #'coerce-name f)
723                                        :test #'string=)))))
724           (let ((do-first (cdr (assoc (class-name (class-of operation))
725                                       (slot-value c 'do-first)))))
726             (loop for (required-op . deps) in do-first
727                   do (do-dep required-op deps)))
728           (setf forced (append (delete 'pruned-op forced :key #'car)
729                                (delete 'pruned-op module-ops :key #'car)
730                                (list (cons operation c))))))
731       (setf (visiting-component operation c) nil)
732       (visit-component operation c (and forced t))
733       forced)))
734  
735
736 (defmethod perform ((operation operation) (c source-file))
737   (sysdef-error
738    "~@<required method PERFORM not implemented ~
739     for operation ~A, component ~A~@:>"
740    (class-of operation) (class-of c)))
741
742 (defmethod perform ((operation operation) (c module))
743   nil)
744
745 (defmethod explain ((operation operation) (component component))
746   (format *verbose-out* "~&;;; ~A on ~A~%" operation component))
747
748 ;;; compile-op
749
750 (defclass compile-op (operation)
751   ((proclamations :initarg :proclamations :accessor compile-op-proclamations :initform nil)
752    (on-warnings :initarg :on-warnings :accessor operation-on-warnings
753                 :initform *compile-file-warnings-behaviour*)
754    (on-failure :initarg :on-failure :accessor operation-on-failure
755                :initform *compile-file-failure-behaviour*)))
756
757 (defmethod perform :before ((operation compile-op) (c source-file))
758   (map nil #'ensure-directories-exist (output-files operation c)))
759
760 (defmethod perform :after ((operation operation) (c component))
761   (setf (gethash (type-of operation) (component-operation-times c))
762         (get-universal-time))
763   (load-preferences c operation))
764
765 ;;; perform is required to check output-files to find out where to put
766 ;;; its answers, in case it has been overridden for site policy
767 (defmethod perform ((operation compile-op) (c cl-source-file))
768   #-:broken-fasl-loader
769   (let ((source-file (component-pathname c))
770         (output-file (car (output-files operation c))))
771     (multiple-value-bind (output warnings-p failure-p)
772                          (compile-file source-file
773                                        :output-file output-file)
774       ;(declare (ignore output))
775       (when warnings-p
776         (case (operation-on-warnings operation)
777           (:warn (warn
778                   "~@<COMPILE-FILE warned while performing ~A on ~A.~@:>"
779                   operation c))
780           (:error (error 'compile-warned :component c :operation operation))
781           (:ignore nil)))
782       (when failure-p
783         (case (operation-on-failure operation)
784           (:warn (warn
785                   "~@<COMPILE-FILE failed while performing ~A on ~A.~@:>"
786                   operation c))
787           (:error (error 'compile-failed :component c :operation operation))
788           (:ignore nil)))
789       (unless output
790         (error 'compile-error :component c :operation operation)))))
791
792 (defmethod output-files ((operation compile-op) (c cl-source-file))
793   #-:broken-fasl-loader (list (compile-file-pathname (component-pathname c)))
794   #+:broken-fasl-loader (list (component-pathname c)))
795
796 (defmethod perform ((operation compile-op) (c static-file))
797   nil)
798
799 (defmethod output-files ((operation compile-op) (c static-file))
800   nil)
801
802 (defmethod input-files ((op compile-op) (c static-file))
803   nil)
804
805
806 ;;; load-op
807
808 (defclass basic-load-op (operation) ())
809
810 (defclass load-op (basic-load-op) ())
811
812 (defmethod perform ((o load-op) (c cl-source-file))
813   (mapcar #'load (input-files o c)))
814
815 (defmethod perform ((operation load-op) (c static-file))
816   nil)
817 (defmethod operation-done-p ((operation load-op) (c static-file))
818   t)
819
820 (defmethod output-files ((o operation) (c component))
821   nil)
822
823 (defmethod component-depends-on ((operation load-op) (c component))
824   (cons (list 'compile-op (component-name c))
825         (call-next-method)))
826
827 ;;; load-source-op
828
829 (defclass load-source-op (basic-load-op) ())
830
831 (defmethod perform ((o load-source-op) (c cl-source-file))
832   (let ((source (component-pathname c)))
833     (setf (component-property c 'last-loaded-as-source)
834           (and (load source)
835                (get-universal-time)))))
836
837 (defmethod perform ((operation load-source-op) (c static-file))
838   nil)
839
840 (defmethod output-files ((operation load-source-op) (c component))
841   nil)
842
843 ;;; FIXME: we simply copy load-op's dependencies.  this is Just Not Right.
844 (defmethod component-depends-on ((o load-source-op) (c component))
845   (let ((what-would-load-op-do (cdr (assoc 'load-op
846                                            (slot-value c 'in-order-to)))))
847     (mapcar (lambda (dep)
848               (if (eq (car dep) 'load-op)
849                   (cons 'load-source-op (cdr dep))
850                   dep))
851             what-would-load-op-do)))
852
853 (defmethod operation-done-p ((o load-source-op) (c source-file))
854   (if (or (not (component-property c 'last-loaded-as-source))
855           (> (file-write-date (component-pathname c))
856              (component-property c 'last-loaded-as-source)))
857       nil t))
858
859 (defclass test-op (operation) ())
860
861 (defmethod perform ((operation test-op) (c component))
862   nil)
863
864 (defgeneric load-preferences (system operation)
865   (:documentation "Called to load system preferences after <perform operation system>. Typical uses are to set parameters that don't exist until after the system has been loaded."))
866
867 (defgeneric preference-file-for-system/operation (system operation)
868   (:documentation "Returns the pathname of the preference file for this system. Called by 'load-preferences to determine what file to load."))
869
870 (defmethod load-preferences ((s t) (operation t))
871   ;; do nothing
872   (values))
873
874 (defmethod load-preferences ((s system) (operation basic-load-op))
875   (let* ((*package* (find-package :common-lisp))
876          (file (probe-file (preference-file-for-system/operation s operation))))
877     (when file
878       (when *verbose-out*
879         (format *verbose-out*
880                 "~&~@<; ~@;loading preferences for ~A/~(~A~) from ~A~@:>~%"
881                 (component-name s)
882                 (type-of operation) file))
883       (load file))))
884
885 (defmethod preference-file-for-system/operation ((system t) (operation t))
886   ;; cope with anything other than systems
887   (preference-file-for-system/operation (find-system system t) operation))
888
889 (defmethod preference-file-for-system/operation ((s system) (operation t))
890   (let ((*default-pathname-defaults*
891          (make-pathname :name nil :type nil
892                         :defaults *default-pathname-defaults*)))
893      (merge-pathnames
894       (make-pathname :name (component-name s)
895                      :type "lisp"
896                      :directory '(:relative ".asdf"))
897       (truename (user-homedir-pathname)))))
898
899 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
900 ;;; invoking operations
901
902 (defvar *operate-docstring*
903   "Operate does three things:
904
905 1. It creates an instance of `operation-class` using any keyword parameters
906 as initargs.
907 2. It finds the  asdf-system specified by `system` (possibly loading
908 it from disk).
909 3. It then calls `traverse` with the operation and system as arguments
910
911 The traverse operation is wrapped in `with-compilation-unit` and error 
912 handling code. If a `version` argument is supplied, then operate also
913 ensures that the system found satisfies it using the `version-satisfies`
914 method.")
915
916 (defun operate (operation-class system &rest args &key (verbose t) version
917                                 &allow-other-keys)
918   (let* ((op (apply #'make-instance operation-class
919                     :original-initargs args
920                     args))
921          (*verbose-out* (if verbose *standard-output* (make-broadcast-stream)))
922          (system (if (typep system 'component) system (find-system system))))
923     (unless (version-satisfies system version)
924       (error 'missing-component :requires system :version version))
925     (let ((steps (traverse op system)))
926       (with-compilation-unit ()
927         (loop for (op . component) in steps do
928              (loop
929                 (restart-case
930                     (progn (perform op component)
931                            (return))
932                   (retry ()
933                     :report
934                     (lambda (s)
935                       (format s "~@<Retry performing ~S on ~S.~@:>"
936                               op component)))
937                   (accept ()
938                     :report
939                     (lambda (s)
940                       (format s
941                               "~@<Continue, treating ~S on ~S as ~
942                                having been successful.~@:>"
943                               op component))
944                     (setf (gethash (type-of op)
945                                    (component-operation-times component))
946                           (get-universal-time))
947                     (return)))))))))
948
949 (setf (documentation 'operate 'function)
950       *operate-docstring*)
951
952 (defun oos (operation-class system &rest args &key force (verbose t) version)
953   (declare (ignore force verbose version))
954   (apply #'operate operation-class system args))
955
956 (setf (documentation 'oos 'function)
957       (format nil
958              "Short for _operate on system_ and an alias for the `operate` function. ~&~&~a"
959       *operate-docstring*))
960
961 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
962 ;;; syntax
963
964 (defun remove-keyword (key arglist)
965   (labels ((aux (key arglist)
966              (cond ((null arglist) nil)
967                    ((eq key (car arglist)) (cddr arglist))
968                    (t (cons (car arglist) (cons (cadr arglist)
969                                                 (remove-keyword
970                                                  key (cddr arglist))))))))
971     (aux key arglist)))
972
973 (defmacro defsystem (name &body options)
974   (destructuring-bind (&key pathname (class 'system) &allow-other-keys) options
975     (let ((component-options (remove-keyword :class options)))
976       `(progn
977         ;; system must be registered before we parse the body, otherwise
978         ;; we recur when trying to find an existing system of the same name
979         ;; to reuse options (e.g. pathname) from
980         (let ((s (system-registered-p ',name)))
981           (cond ((and s (eq (type-of (cdr s)) ',class))
982                  (setf (car s) (get-universal-time)))
983                 (s
984                  #+clisp
985                  (sysdef-error "Cannot redefine the existing system ~A with a different class" s)
986                  #-clisp
987                  (change-class (cdr s) ',class))
988                 (t
989                  (register-system (quote ,name)
990                                   (make-instance ',class :name ',name)))))
991         (parse-component-form nil (apply
992                                    #'list
993                                    :module (coerce-name ',name)
994                                    :pathname
995                                    (or ,pathname
996                                        (when *load-truename*
997                                          (pathname-sans-name+type
998                                           (resolve-symlinks  *load-truename*)))
999                                        *default-pathname-defaults*)
1000                                    ',component-options))))))
1001  
1002
1003 (defun class-for-type (parent type)
1004   (let* ((extra-symbols (list (find-symbol (symbol-name type) *package*)
1005                               (find-symbol (symbol-name type)
1006                                            (load-time-value
1007                                             (package-name :asdf)))))
1008          (class (dolist (symbol (if (keywordp type)
1009                                     extra-symbols
1010                                     (cons type extra-symbols)))
1011                   (when (and symbol
1012                              (find-class symbol nil)
1013                              (subtypep symbol 'component))
1014                     (return (find-class symbol))))))
1015     (or class
1016         (and (eq type :file)
1017              (or (module-default-component-class parent)
1018                  (find-class 'cl-source-file)))
1019         (sysdef-error "~@<don't recognize component type ~A~@:>" type))))
1020
1021 (defun maybe-add-tree (tree op1 op2 c)
1022   "Add the node C at /OP1/OP2 in TREE, unless it's there already.
1023 Returns the new tree (which probably shares structure with the old one)"
1024   (let ((first-op-tree (assoc op1 tree)))
1025     (if first-op-tree
1026         (progn
1027           (aif (assoc op2 (cdr first-op-tree))
1028                (if (find c (cdr it))
1029                    nil
1030                    (setf (cdr it) (cons c (cdr it))))
1031                (setf (cdr first-op-tree)
1032           &nbs