0.7.12.53:
[sbcl.git] / contrib / asdf / asdf.lisp
1 ;;; This is asdf: Another System Definition Facility.  $\Revision: 1.59 $
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-2003 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            
44            #:compile-op #:load-op #:load-source-op #:test-system-version
45            #:operation                  ; operations
46            #:feature                    ; sort-of operation
47            #:version                    ; metaphorically sort-of an operation
48            
49            #:output-files #:perform     ; operation methods
50            #:operation-done-p #:explain
51            
52            #:component #:source-file 
53            #:c-source-file #:cl-source-file #:java-source-file
54            #:static-file
55            #:doc-file
56            #:html-file
57            #:text-file
58            #:source-file-type
59            #:module                     ; components
60            #:system
61            #:unix-dso
62            
63            #:module-components          ; component accessors
64            #:component-pathname
65            #:component-relative-pathname
66            #:component-name
67            #:component-version
68            #:component-parent
69            #:component-property
70            
71            #:component-depends-on
72            
73            ;#:*component-parent-pathname* 
74            #:*central-registry*         ; variables
75            
76            #:operation-error #:compile-failed #:compile-warned #:compile-error
77            #:system-definition-error 
78            #:missing-component
79            #:missing-dependency
80            #:circular-dependency        ; errors
81            )
82   (:use :cl))
83
84 #+nil
85 (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")
86
87
88 (in-package #:asdf)
89
90 (defvar *asdf-revision* (let* ((v "$\Revision: 1.59 $")
91                                (colon (position #\: v))
92                                (dot (position #\. v)))
93                           (and v colon dot 
94                                (list (parse-integer v :start (1+ colon)
95                                                     :junk-allowed t)
96                                      (parse-integer v :start (1+ dot)
97                                                     :junk-allowed t)))))
98
99 (defvar  *compile-file-warnings-behaviour* :warn)
100 (defvar  *compile-file-failure-behaviour* #+sbcl :error #-sbcl :warn)
101
102 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
103 ;; utility stuff
104
105 (defmacro aif (test then &optional else)
106   `(let ((it ,test)) (if it ,then ,else)))
107
108 (defun pathname-sans-name+type (pathname)
109   "Returns a new pathname with same HOST, DEVICE, DIRECTORY as PATHNAME,
110 and NIL NAME and TYPE components"
111   (make-pathname :name nil :type nil :defaults pathname))
112
113 (define-modify-macro appendf (&rest args) 
114                      append "Append onto list") 
115
116 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
117 ;; classes, condiitons
118
119 (define-condition system-definition-error (error) ()
120   ;; [this use of :report should be redundant, but unfortunately it's not.
121   ;; cmucl's lisp::output-instance prefers the kernel:slot-class-print-function
122   ;; over print-object; this is always conditions::%print-condition for
123   ;; condition objects, which in turn does inheritance of :report options at
124   ;; run-time.  fortunately, inheritance means we only need this kludge here in
125   ;; order to fix all conditions that build on it.  -- rgr, 28-Jul-02.]
126   #+cmu (:report print-object))
127
128 (define-condition formatted-system-definition-error (system-definition-error)
129   ((format-control :initarg :format-control :reader format-control)
130    (format-arguments :initarg :format-arguments :reader format-arguments))
131   (:report (lambda (c s)
132              (apply #'format s (format-control c) (format-arguments c)))))
133
134 (define-condition circular-dependency (system-definition-error)
135   ((components :initarg :components :reader circular-dependency-components)))
136
137 (define-condition missing-component (system-definition-error)
138   ((requires :initform "(unnamed)" :reader missing-requires :initarg :requires)
139    (version :initform nil :reader missing-version :initarg :version)
140    (parent :initform nil :reader missing-parent :initarg :parent)))
141
142 (define-condition missing-dependency (missing-component)
143   ((required-by :initarg :required-by :reader missing-required-by)))
144
145 (define-condition operation-error (error)
146   ((component :reader error-component :initarg :component)
147    (operation :reader error-operation :initarg :operation))
148   (:report (lambda (c s)
149              (format s (formatter "~@<erred while invoking ~A on ~A~@:>")
150                      (error-operation c) (error-component c)))))
151 (define-condition compile-error (operation-error) ())
152 (define-condition compile-failed (compile-error) ())
153 (define-condition compile-warned (compile-error) ())
154
155 (defclass component ()
156   ((name :type string :accessor component-name :initarg :name :documentation
157          "Component name, restricted to portable pathname characters")
158    (version :accessor component-version :initarg :version)
159    (in-order-to :initform nil :initarg :in-order-to)
160    ;;; XXX crap name
161    (do-first :initform nil :initarg :do-first)
162    ;; methods defined using the "inline" style inside a defsystem form:
163    ;; need to store them somewhere so we can delete them when the system
164    ;; is re-evaluated
165    (inline-methods :accessor component-inline-methods :initform nil)
166    (parent :initarg :parent :initform nil :reader component-parent)
167    ;; no direct accessor for pathname, we do this as a method to allow
168    ;; it to default in funky ways if not supplied
169    (relative-pathname :initarg :pathname)
170    (operation-times :initform (make-hash-table )
171                     :accessor component-operation-times)
172    ;; XXX we should provide some atomic interface for updating the
173    ;; component properties
174    (properties :accessor component-properties :initarg :properties
175                :initform nil)))
176
177 ;;;; methods: conditions
178
179 (defmethod print-object ((c missing-dependency) s)
180   (format s (formatter "~@<~A, required by ~A~@:>")
181           (call-next-method c nil)
182           (missing-required-by c)))
183
184 (defun sysdef-error (format &rest arguments)
185   (error 'formatted-system-definition-error :format-control format :format-arguments arguments))
186
187 ;;;; methods: components
188
189 (defmethod print-object ((c missing-component) s)
190   (format s (formatter "~@<component ~S not found~
191                         ~@[ or does not match version ~A~]~
192                         ~@[ in ~A~]~@:>")
193           (missing-requires c)
194           (missing-version c)
195           (when (missing-parent c)
196             (component-name (missing-parent c)))))
197
198 (defgeneric component-system (component)
199   (:documentation "Find the top-level system containing COMPONENT"))
200   
201 (defmethod component-system ((component component))
202   (aif (component-parent component)
203        (component-system it)
204        component))
205
206 (defmethod print-object ((c component) stream)
207   (print-unreadable-object (c stream :type t :identity t)
208     (ignore-errors
209       (prin1 (component-name c) stream))))
210
211 (defclass module (component)
212   ((components :initform nil :accessor module-components :initarg :components)
213    ;; what to do if we can't satisfy a dependency of one of this module's
214    ;; components.  This allows a limited form of conditional processing
215    (if-component-dep-fails :initform :fail
216                            :accessor module-if-component-dep-fails
217                            :initarg :if-component-dep-fails)
218    (default-component-class :accessor module-default-component-class
219      :initform 'cl-source-file :initarg :default-component-class)))
220
221 (defgeneric component-pathname (component)
222   (:documentation "Extracts the pathname applicable for a particular component."))
223
224 (defun component-parent-pathname (component)
225   (aif (component-parent component)
226        (component-pathname it)
227        *default-pathname-defaults*))
228
229 (defgeneric component-relative-pathname (component)
230   (:documentation "Extracts the relative pathname applicable for a particular component."))
231    
232 (defmethod component-relative-pathname ((component module))
233   (or (slot-value component 'relative-pathname)
234       (make-pathname
235        :directory `(:relative ,(component-name component))
236        :host (pathname-host (component-parent-pathname component)))))
237
238 (defmethod component-pathname ((component component))
239   (let ((*default-pathname-defaults* (component-parent-pathname component)))
240     (merge-pathnames (component-relative-pathname component))))
241
242 (defgeneric component-property (component property))
243
244 (defmethod component-property ((c component) property)
245   (cdr (assoc property (slot-value c 'properties))))
246
247 (defgeneric (setf component-property) (new-value component property))
248
249 (defmethod (setf component-property) (new-value (c component) property)
250   (let ((a (assoc property (slot-value c 'properties))))
251     (if a
252         (setf (cdr a) new-value)
253         (setf (slot-value c 'properties)
254               (acons property new-value (slot-value c 'properties))))))
255
256
257
258 (defclass system (module)
259   ((description :accessor system-description :initarg :description)
260    (long-description :accessor long-description :initarg :long-description)
261    (author :accessor system-author :initarg :author)
262    (maintainer :accessor system-maintainer :initarg :maintainer)
263    (licence :accessor system-licence :initarg :licence)))
264
265 ;;; version-satisfies
266
267 ;;; with apologies to christophe rhodes ...
268 (defun split (string &optional max (ws '(#\Space #\Tab)))
269   (flet ((is-ws (char) (find char ws)))
270     (nreverse
271      (let ((list nil) (start 0) (words 0) end)
272        (loop
273         (when (and max (>= words (1- max)))
274           (return (cons (subseq string start) list)))
275         (setf end (position-if #'is-ws string :start start))
276         (push (subseq string start end) list)
277         (incf words)
278         (unless end (return list))
279         (setf start (1+ end)))))))
280
281 (defgeneric version-satisfies (component version))
282
283 (defmethod version-satisfies ((c component) version)
284   (unless (and version (slot-boundp c 'version))
285     (return-from version-satisfies t))
286   (let ((x (mapcar #'parse-integer
287                    (split (component-version c) nil '(#\.))))
288         (y (mapcar #'parse-integer
289                    (split version nil '(#\.)))))
290     (labels ((bigger (x y)
291                (cond ((not y) t)
292                      ((not x) nil)
293                      ((> (car x) (car y)) t)
294                      ((= (car x) (car y))
295                       (bigger (cdr x) (cdr y))))))
296       (and (= (car x) (car y))
297            (or (not (cdr y)) (bigger (cdr x) (cdr y)))))))
298
299 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
300 ;;; finding systems
301
302 (defvar *defined-systems* (make-hash-table :test 'equal))
303 (defun coerce-name (name)
304    (typecase name
305      (component (component-name name))
306      (symbol (string-downcase (symbol-name name)))
307      (string name)
308      (t (sysdef-error (formatter "~@<invalid component designator ~A~@:>")
309                       name))))
310
311 (defun system-definition-pathname (system)
312   (some (lambda (x) (funcall x system))
313         *system-definition-search-functions*))
314         
315 (defun sysdef-central-registry-search (system)
316   (let ((name (coerce-name system)))
317     (block nil
318       (dolist (dir *central-registry*)
319         (let* ((defaults (eval dir))
320                (file (and defaults
321                           (make-pathname
322                            :defaults defaults :version :newest
323                            :name name :type "asd" :case :local))))
324           (if (and file (probe-file file))
325               (return file)))))))
326
327
328 (defvar *central-registry*
329   '(*default-pathname-defaults*
330     #+nil "/home/dan/src/sourceforge/cclan/asdf/systems/"
331     #+nil "telent:asdf;systems;"))
332
333 ;;; for the sake of keeping things reasonably neat, we adopt a
334 ;;; convention that functions in this list are prefixed SYSDEF-
335
336 (defvar *system-definition-search-functions*
337   '(sysdef-central-registry-search))
338
339 (defun find-system (name &optional (error-p t))
340   (let* ((name (coerce-name name))
341          (in-memory (gethash name *defined-systems*))
342          (on-disk (system-definition-pathname name)))    
343     (when (and on-disk
344                (or (not in-memory)
345                    (< (car in-memory) (file-write-date on-disk))))
346       (let ((*package* (make-package (gensym (package-name #.*package*))
347                                      :use '(:cl :asdf))))
348         (format t
349                 (formatter "~&~@<; ~@;loading system definition from ~A into ~A~@:>~%")
350                 ;; FIXME: This wants to be (ENOUGH-NAMESTRING
351                 ;; ON-DISK), but CMUCL barfs on that.
352                 on-disk
353                 *package*)
354         (load on-disk)))
355     (let ((in-memory (gethash name *defined-systems*)))
356       (if in-memory
357           (progn (if on-disk (setf (car in-memory) (file-write-date on-disk)))
358                  (cdr in-memory))
359           (if error-p (error 'missing-component :requires name))))))
360
361 (defun register-system (name system)
362   (format t (formatter "~&~@<; ~@;registering ~A as ~A~@:>~%") system name)
363   (setf (gethash (coerce-name  name) *defined-systems*)
364         (cons (get-universal-time) system)))
365
366 (defun system-registered-p (name)
367   (gethash (coerce-name name) *defined-systems*))
368
369 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
370 ;;; finding components
371
372 (defgeneric find-component (module name &optional version)
373   (:documentation "Finds the component with name NAME present in the
374 MODULE module; if MODULE is nil, then the component is assumed to be a
375 system."))
376
377 (defmethod find-component ((module module) name &optional version)
378   (if (slot-boundp module 'components)
379       (let ((m (find name (module-components module)
380                      :test #'equal :key #'component-name)))
381         (if (and m (version-satisfies m version)) m))))
382             
383
384 ;;; a component with no parent is a system
385 (defmethod find-component ((module (eql nil)) name &optional version)
386   (let ((m (find-system name nil)))
387     (if (and m (version-satisfies m version)) m)))
388
389 ;;; component subclasses
390
391 (defclass source-file (component) ())
392
393 (defclass cl-source-file (source-file) ())
394 (defclass c-source-file (source-file) ())
395 (defclass java-source-file (source-file) ())
396 (defclass static-file (source-file) ())
397 (defclass doc-file (static-file) ())
398 (defclass html-file (doc-file) ())
399
400 (defgeneric source-file-type (component system))
401 (defmethod source-file-type ((c cl-source-file) (s module)) "lisp")
402 (defmethod source-file-type ((c c-source-file) (s module)) "c")
403 (defmethod source-file-type ((c java-source-file) (s module)) "java")
404 (defmethod source-file-type ((c html-file) (s module)) "html")
405 (defmethod source-file-type ((c static-file) (s module)) nil)
406
407 (defmethod component-relative-pathname ((component source-file))
408   (let ((*default-pathname-defaults* (component-parent-pathname component)))
409     (or (slot-value component 'relative-pathname)
410         (make-pathname :name (component-name component)
411                        :type
412                        (source-file-type component
413                                          (component-system component))))))
414
415
416 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
417 ;;; operations
418
419 ;;; one of these is instantiated whenever (operate ) is called
420
421 (defclass operation ()
422   ((forced-p :initform nil :initarg :force :accessor operation-forced-p )
423    (original-initargs :initform nil :initarg :original-initargs
424                       :accessor operation-original-initargs)
425    (visited-nodes :initform nil :accessor operation-visited-nodes)
426    (visiting-nodes :initform nil :accessor operation-visiting-nodes)
427    (parent :initform nil :initarg :parent :accessor operation-parent)))
428
429 (defmethod shared-initialize :after ((operation operation) slot-names
430                                      &key force 
431                                      &allow-other-keys)
432   (declare (ignore slot-names force))
433   ;; empty method to disable initarg validity checking
434   )
435
436 (defgeneric perform (operation component))
437 (defgeneric operation-done-p (operation component))
438 (defgeneric explain (operation component))
439 (defgeneric output-files (operation component))
440 (defgeneric input-files (operation component))
441
442 (defun node-for (o c)
443   (cons (class-name (class-of o)) c))
444
445 (defgeneric operation-ancestor (operation)
446   (:documentation   "Recursively chase the operation's parent pointer until we get to the head of the tree"))
447
448 (defmethod operation-ancestor ((operation operation))
449   (aif (operation-parent operation)
450        (operation-ancestor it)
451        operation))
452
453 (defun make-sub-operation (o type)
454   (let ((args (operation-original-initargs o)))
455     (apply #'make-instance type :parent o :original-initargs args args)))
456
457 (defgeneric visit-component (operation component data))
458
459 (defmethod visit-component ((o operation) (c component) data)
460   (unless (component-visited-p o c)
461     (push (cons (node-for o c) data)
462           (operation-visited-nodes (operation-ancestor o)))))
463
464 (defgeneric component-visited-p (operation component))
465
466 (defmethod component-visited-p ((o operation) (c component))
467   (assoc (node-for o c)
468          (operation-visited-nodes (operation-ancestor o))
469          :test 'equal))
470
471 (defgeneric (setf visiting-component) (new-value operation component))
472
473 (defmethod (setf visiting-component) (new-value operation component)
474   ;; MCL complains about unused lexical variables
475   (declare (ignorable new-value operation component)))
476
477 (defmethod (setf visiting-component) (new-value (o operation) (c component))
478   (let ((node (node-for o c))
479         (a (operation-ancestor o)))
480     (if new-value
481         (pushnew node (operation-visiting-nodes a) :test 'equal)
482         (setf (operation-visiting-nodes a)
483               (remove node  (operation-visiting-nodes a) :test 'equal)))))
484
485 (defgeneric component-visiting-p (operation component))
486
487 (defmethod component-visiting-p ((o operation) (c component))
488   (let ((node (cons o c)))
489     (member node (operation-visiting-nodes (operation-ancestor o))
490             :test 'equal)))
491
492 (defgeneric component-depends-on (operation component))
493
494 (defmethod component-depends-on ((o operation) (c component))
495   (cdr (assoc (class-name (class-of o))
496               (slot-value c 'in-order-to))))
497
498 (defgeneric component-self-dependencies (operation component))
499
500 (defmethod component-self-dependencies ((o operation) (c component))
501   (let ((all-deps (component-depends-on o c)))
502     (remove-if-not (lambda (x)
503                      (member (component-name c) (cdr x) :test #'string=))
504                    all-deps)))
505     
506 (defmethod input-files ((operation operation) (c component))
507   (let ((parent (component-parent c))
508         (self-deps (component-self-dependencies operation c)))
509     (if self-deps
510         (mapcan (lambda (dep)
511                   (destructuring-bind (op name) dep
512                     (output-files (make-instance op)
513                                   (find-component parent name))))
514                 self-deps)
515         ;; no previous operations needed?  I guess we work with the 
516         ;; original source file, then
517         (list (component-pathname c)))))
518
519 (defmethod input-files ((operation operation) (c module)) nil)
520
521 (defmethod operation-done-p ((o operation) (c component))
522   (let ((out-files (output-files o c))
523         (in-files (input-files o c)))
524     (cond ((and (not in-files) (not out-files))
525            ;; arbitrary decision: an operation that uses nothing to
526            ;; produce nothing probably isn't doing much 
527            t)
528           ((not out-files) 
529            (let ((op-done
530                   (gethash (type-of o)
531                            (component-operation-times c))))
532              (and op-done
533                   (>= op-done
534                       (or (apply #'max
535                                  (mapcar #'file-write-date in-files)) 0)))))
536           ((not in-files) nil)
537           (t
538            (and
539             (every #'probe-file out-files)
540             (> (apply #'min (mapcar #'file-write-date out-files))
541                (apply #'max (mapcar #'file-write-date in-files)) ))))))
542
543 ;;; So you look at this code and think "why isn't it a bunch of
544 ;;; methods".  And the answer is, because standard method combination
545 ;;; runs :before methods most->least-specific, which is back to front
546 ;;; for our purposes.  And CLISP doesn't have non-standard method
547 ;;; combinations, so let's keep it simple and aspire to portability
548
549 (defgeneric traverse (operation component))
550 (defmethod traverse ((operation operation) (c component))
551   (let ((forced nil))
552     (labels ((do-one-dep (required-op required-c required-v)
553                (let ((op (if (subtypep (type-of operation) required-op)
554                              operation
555                              (make-sub-operation operation required-op)))
556                      (dep-c (or (find-component
557                                  (component-parent c)
558                                  ;; XXX tacky.  really we should build the
559                                  ;; in-order-to slot with canonicalized
560                                  ;; names instead of coercing this late
561                                  (coerce-name required-c) required-v)
562                                 (error 'missing-dependency :required-by c
563                                        :version required-v
564                                        :requires required-c))))
565                  (traverse op dep-c)))             
566              (do-dep (op dep)
567                (cond ((eq op 'feature)
568                       (or (member (car dep) *features*)
569                           (error 'missing-dependency :required-by c
570                                  :requires (car dep) :version nil)))
571                      (t
572                       (dolist (d dep)
573                         (cond ((consp d)
574                                (assert (string-equal
575                                         (symbol-name (first d))
576                                         "VERSION"))
577                                (appendf forced
578                                         (do-one-dep op (second d) (third d))))
579                               (t
580                                (appendf forced (do-one-dep op d nil)))))))))
581       (aif (component-visited-p operation c)
582            (return-from traverse
583              (if (cdr it) (list (cons 'pruned-op c)) nil)))
584       ;; dependencies
585       (if (component-visiting-p operation c)
586           (error 'circular-dependency :components (list c)))
587       (setf (visiting-component operation c) t)
588       (loop for (required-op . deps) in (component-depends-on operation c)
589             do (do-dep required-op deps))
590       ;; constituent bits
591       (let ((module-ops
592              (when (typep c 'module)
593                (let ((at-least-one nil)
594                      (forced nil)
595                      (error nil))
596                  (loop for kid in (module-components c)
597                        do (handler-case
598                               (appendf forced (traverse operation kid ))
599                             (missing-dependency (condition)
600                               (if (eq (module-if-component-dep-fails c) :fail)
601                                   (error condition))
602                               (setf error condition))
603                             (:no-error (c)
604                               (declare (ignore c))
605                               (setf at-least-one t))))
606                  (when (and (eq (module-if-component-dep-fails c) :try-next)
607                             (not at-least-one))
608                    (error error))
609                  forced))))
610         ;; now the thing itself
611         (when (or forced module-ops
612                   (operation-forced-p (operation-ancestor operation))
613                   (not (operation-done-p operation c)))
614           (let ((do-first (cdr (assoc (class-name (class-of operation))
615                                       (slot-value c 'do-first)))))
616             (loop for (required-op . deps) in do-first
617                   do (do-dep required-op deps)))
618           (setf forced (append (delete 'pruned-op forced :key #'car)
619                                (delete 'pruned-op module-ops :key #'car)
620                                (list (cons operation c))))))
621       (setf (visiting-component operation c) nil)
622       (visit-component operation c (and forced t))
623       forced)))
624   
625
626 (defmethod perform ((operation operation) (c source-file))
627   (sysdef-error
628    (formatter "~@<required method PERFORM not implemented~
629                for operation ~A, component ~A~@:>")
630    (class-of operation) (class-of c)))
631
632 (defmethod perform ((operation operation) (c module))
633   nil)
634
635 (defmethod explain ((operation operation) (component component))
636   (format *trace-output* "~&;;; ~A on ~A~%"
637           operation component))
638
639 ;;; compile-op
640
641 (defclass compile-op (operation)
642   ((proclamations :initarg :proclamations :accessor compile-op-proclamations :initform nil)
643    (on-warnings :initarg :on-warnings :accessor operation-on-warnings
644                 :initform *compile-file-warnings-behaviour*)
645    (on-failure :initarg :on-failure :accessor operation-on-failure
646                :initform *compile-file-failure-behaviour*)))
647
648 (defmethod perform :before ((operation compile-op) (c source-file))
649   (map nil #'ensure-directories-exist (output-files operation c)))
650
651 (defmethod perform :after ((operation operation) (c component))
652   (setf (gethash (type-of operation) (component-operation-times c))
653         (get-universal-time)))
654
655 ;;; perform is required to check output-files to find out where to put
656 ;;; its answers, in case it has been overridden for site policy
657 (defmethod perform ((operation compile-op) (c cl-source-file))
658   (let ((source-file (component-pathname c))
659         (output-file (car (output-files operation c))))
660     (multiple-value-bind (output warnings-p failure-p)
661         (compile-file source-file
662                       :output-file output-file)
663       ;(declare (ignore output))
664       (when warnings-p
665         (case (operation-on-warnings operation)
666           (:warn (warn "COMPILE-FILE warned while performing ~A on ~A"
667                        c operation))
668           (:error (error 'compile-warned :component c :operation operation))
669           (:ignore nil)))
670       (when failure-p
671         (case (operation-on-failure operation)
672           (:warn (warn "COMPILE-FILE failed while performing ~A on ~A"
673                        c operation))
674           (:error (error 'compile-failed :component c :operation operation))
675           (:ignore nil)))
676       (unless output
677         (error 'compile-error :component c :operation operation)))))
678
679 (defmethod output-files ((operation compile-op) (c cl-source-file))
680   (list (compile-file-pathname (component-pathname c))))
681
682 (defmethod perform ((operation compile-op) (c static-file))
683   nil)
684
685 (defmethod output-files ((operation compile-op) (c static-file))
686   nil)
687
688 ;;; load-op
689
690 (defclass load-op (operation) ())
691
692 (defmethod perform ((o load-op) (c cl-source-file))
693   (mapcar #'load (input-files o c)))
694
695 (defmethod perform ((operation load-op) (c static-file))
696   nil)
697 (defmethod operation-done-p ((operation load-op) (c static-file))
698   t)
699
700 (defmethod output-files ((o operation) (c component))
701   nil)
702
703 (defmethod component-depends-on ((operation load-op) (c component))
704   (cons (list 'compile-op (component-name c))
705         (call-next-method)))
706
707 ;;; load-source-op
708
709 (defclass load-source-op (operation) ())
710
711 (defmethod perform ((o load-source-op) (c cl-source-file))
712   (load (component-pathname c)))
713
714
715 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
716 ;;; invoking operations
717
718 (defun operate (operation-class system &rest args)
719   (let* ((op (apply #'make-instance operation-class
720                     :original-initargs args args))
721          (system (if (typep system 'component) system (find-system system)))
722          (steps (traverse op system)))
723     (with-compilation-unit ()
724       (loop for (op . component) in steps do
725             (loop
726              (restart-case 
727                  (progn (perform op component)
728                         (return))
729                (retry-component ())
730                (skip-component () (return))))))))
731
732 (defun oos (&rest args)
733   "Alias of OPERATE function"
734   (apply #'operate args))
735
736 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
737 ;;; syntax
738
739 (defun remove-keyword (key arglist)
740   (labels ((aux (key arglist)
741              (cond ((null arglist) nil)
742                    ((eq key (car arglist)) (cddr arglist))
743                    (t (cons (car arglist) (cons (cadr arglist)
744                                                 (remove-keyword
745                                                  key (cddr arglist))))))))
746     (aux key arglist)))
747
748 (defmacro defsystem (name &body options)
749   (destructuring-bind (&key pathname (class 'system) &allow-other-keys) options
750     (let ((component-options (remove-keyword :class options)))
751       `(progn
752         ;; system must be registered before we parse the body, otherwise
753         ;; we recur when trying to find an existing system of the same name
754         ;; to reuse options (e.g. pathname) from
755         (let ((s (system-registered-p ',name)))
756           (cond ((and s (eq (type-of (cdr s)) ',class))
757                  (setf (car s) (get-universal-time)))
758                 (s
759                  #+clisp
760                  (sysdef-error "Cannot redefine the existing system ~A with a different class" s)
761                  #-clisp
762                  (change-class (cdr s) ',class))
763                 (t
764                  (register-system (quote ,name)
765                                   (make-instance ',class :name ',name)))))
766         (parse-component-form nil (apply
767                                    #'list
768                                    :module (coerce-name ',name)
769                                    :pathname
770                                    (or ,pathname
771                                        (pathname-sans-name+type
772                                         (resolve-symlinks *load-truename*))
773                                        *default-pathname-defaults*)
774                                    ',component-options))))))
775   
776
777 (defun class-for-type (parent type)
778   (let ((class (find-class
779                 (or (find-symbol (symbol-name type) *package*)
780                     (find-symbol (symbol-name type) #.*package*)) nil)))
781     (or class
782         (and (eq type :file)
783              (or (module-default-component-class parent)
784                  (find-class 'cl-source-file)))
785         (sysdef-error (formatter "~@<don't recognize component type ~A~@:>")
786                       type))))
787
788 (defun maybe-add-tree (tree op1 op2 c)
789   "Add the node C at /OP1/OP2 in TREE, unless it's there already.
790 Returns the new tree (which probably shares structure with the old one)"
791   (let ((first-op-tree (assoc op1 tree)))
792     (if first-op-tree
793         (progn
794           (aif (assoc op2 (cdr first-op-tree))
795                (if (find c (cdr it))
796                    nil
797                    (setf (cdr it) (cons c (cdr it))))
798                (setf (cdr first-op-tree)
799                      (acons op2 (list c) (cdr first-op-tree))))
800           tree)
801         (acons op1 (list (list op2 c)) tree))))
802                 
803 (defun union-of-dependencies (&rest deps)
804   (let ((new-tree nil))
805     (dolist (dep deps)
806       (dolist (op-tree dep)
807         (dolist (op  (cdr op-tree))
808           (dolist (c (cdr op))
809             (setf new-tree
810                   (maybe-add-tree new-tree (car op-tree) (car op) c))))))
811     new-tree))
812
813
814 (defun remove-keys (key-names args)
815   (loop for ( name val ) on args by #'cddr
816         unless (member (symbol-name name) key-names 
817                        :key #'symbol-name :test 'equal)
818         append (list name val)))
819
820 (defvar *serial-depends-on*)
821
822 (defun parse-component-form (parent options)
823   (destructuring-bind
824         (type name &rest rest &key
825               ;; the following list of keywords is reproduced below in the
826               ;; remove-keys form.  important to keep them in sync
827               components pathname default-component-class
828               perform explain output-files operation-done-p
829               depends-on serial in-order-to
830               ;; list ends
831               &allow-other-keys) options
832     (let* ((other-args (remove-keys
833                         '(components pathname default-component-class
834                           perform explain output-files operation-done-p
835                           depends-on serial in-order-to)
836                         rest))
837            (ret
838             (or (find-component parent name)
839                 (make-instance (class-for-type parent type)))))
840       (when (boundp '*serial-depends-on*)
841         (setf depends-on
842               (concatenate 'list *serial-depends-on* depends-on)))
843       (apply #'reinitialize-instance
844              ret
845              :name (coerce-name name)
846              :pathname pathname
847              :parent parent
848              other-args)
849       (when (typep ret 'module)
850         (setf (module-default-component-class ret)
851               (or default-component-class
852                   (and (typep parent 'module)
853                        (module-default-component-class parent))))
854         (let ((*serial-depends-on* nil))
855           (setf (module-components ret)
856                 (loop for c-form in components
857                       for c = (parse-component-form ret c-form)
858                       collect c
859                       if serial
860                       do (push (component-name c) *serial-depends-on*)))))
861       
862       (setf (slot-value ret 'in-order-to)
863             (union-of-dependencies
864              in-order-to
865              `((compile-op (compile-op ,@depends-on))
866                (load-op (load-op ,@depends-on))))
867             (slot-value ret 'do-first) `((compile-op (load-op ,@depends-on))))
868       
869       (loop for (n v) in `((perform ,perform) (explain ,explain)
870                            (output-files ,output-files)
871                            (operation-done-p ,operation-done-p))
872             do (map 'nil
873                     ;; this is inefficient as most of the stored
874                     ;; methods will not be for this particular gf n
875                     ;; But this is hardly performance-critical
876                     (lambda (m) (remove-method (symbol-function n) m))
877                     (component-inline-methods ret))
878             when v
879             do (destructuring-bind (op qual (o c) &body body) v
880                  (pushnew
881                   (eval `(defmethod ,n ,qual ((,o ,op) (,c (eql ,ret)))
882                           ,@body))
883                   (component-inline-methods ret))))
884       ret)))
885
886
887 (defun resolve-symlinks (path)
888   #-allegro (truename path)
889   #+allegro (excl:pathname-resolve-symbolic-links path)
890   )
891
892 ;;; optional extras
893
894 ;;; run-shell-command functions for other lisp implementations will be
895 ;;; gratefully accepted, if they do the same thing.  If the docstring
896 ;;; is ambiguous, send a bug report
897
898 (defun run-shell-command (control-string &rest args)
899   "Interpolate ARGS into CONTROL-STRING as if by FORMAT, and
900 synchronously execute the result using a Bourne-compatible shell, with
901 output to *trace-output*.  Returns the shell's exit code."
902   (let ((command (apply #'format nil control-string args)))
903     (format *trace-output* "; $ ~A~%" command)
904     #+sbcl
905     (sb-impl::process-exit-code
906      (sb-ext:run-program  
907       "/bin/sh"
908       (list  "-c" command)
909       :input nil :output *trace-output*))
910     
911     #+(or cmu scl)
912     (ext:process-exit-code
913      (ext:run-program  
914       "/bin/sh"
915       (list  "-c" command)
916       :input nil :output *trace-output*))
917
918     #+allegro
919     (excl:run-shell-command command :input nil :output *trace-output*)
920     
921     #+lispworks
922     (system:call-system-showing-output
923      command
924      :shell-type "/bin/sh"
925      :output-stream *trace-output*)
926     
927     #+clisp                             ;XXX not exactly *trace-output*, I know
928     (ext:run-shell-command  command :output :terminal :wait t)
929
930     #+openmcl
931     (nth-value 1
932                (ccl:external-process-status
933                 (ccl:run-program "/bin/sh" (list "-c" command)
934                                  :input nil :output *trace-output*
935                                  :wait t)))
936
937     #-(or openmcl clisp lispworks allegro scl cmu sbcl)
938     (error "RUN-SHELL-PROGRAM not implemented for this Lisp")
939     ))
940
941 (pushnew :asdf *features*)
942
943 #+sbcl
944 (eval-when (:compile-toplevel :load-toplevel :execute)
945   (when (sb-ext:posix-getenv "SBCL_BUILDING_CONTRIB")
946     (pushnew :sbcl-hooks-require *features*)))
947
948 #+(and sbcl sbcl-hooks-require)
949 (progn
950   (defun module-provide-asdf (name)
951     (let ((system (asdf:find-system name nil)))
952       (when system
953         (asdf:operate 'asdf:load-op name)
954         (provide name))))
955
956   (pushnew
957    (merge-pathnames "systems/"
958                     (truename (sb-ext:posix-getenv "SBCL_HOME")))
959    *central-registry*)
960   
961   (pushnew
962    (merge-pathnames "site-systems/"
963                     (truename (sb-ext:posix-getenv "SBCL_HOME")))
964    *central-registry*)
965   
966   (pushnew
967    (merge-pathnames ".sbcl/systems"
968                     (user-homedir-pathname))
969    *central-registry*)
970   
971   (pushnew 'module-provide-asdf sb-ext:*module-provider-functions*))