1 ;;; This is asdf: Another System Definition Facility. $Revision$
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/>
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'
16 ;;; Copyright (c) 2001-2003 Daniel Barlow and contributors
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:
26 ;;; The above copyright notice and this permission notice shall be
27 ;;; included in all copies or substantial portions of the Software.
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.
37 ;;; the problem with writing a defsystem replacement is bootstrapping:
38 ;;; we can't use defsystem to compile it. Hence, all in one file
41 (:export
#:defsystem
#:oos
#:operate
#:find-system
#:run-shell-command
42 #:system-definition-pathname
#:find-component
; miscellaneous
43 #:hyperdocumentation
#:hyperdoc
45 #:compile-op
#:load-op
#:load-source-op
#:test-system-version
47 #:operation
; operations
48 #:feature
; sort-of operation
49 #:version
; metaphorically sort-of an operation
51 #:input-files
#:output-files
#:perform
; operation methods
52 #:operation-done-p
#:explain
54 #:component
#:source-file
55 #:c-source-file
#:cl-source-file
#:java-source-file
65 #:module-components
; component accessors
67 #:component-relative-pathname
74 #:component-depends-on
77 #:system-long-description
82 #:operation-on-warnings
83 #:operation-on-failure
85 ;#:*component-parent-pathname*
86 #:*system-definition-search-functions
*
87 #:*central-registry
* ; variables
88 #:*compile-file-warnings-behaviour
*
89 #:*compile-file-failure-behaviour
*
92 #:operation-error
#:compile-failed
#:compile-warned
#:compile-error
93 #:error-component
#:error-operation
94 #:system-definition-error
97 #:circular-dependency
; errors
106 (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")
111 (defvar *asdf-revision
* (let* ((v "$Revision$")
112 (colon (or (position #\
: v
) -
1))
113 (dot (position #\. v
)))
115 (list (parse-integer v
:start
(1+ colon
)
117 (parse-integer v
:start
(1+ dot
)
120 (defvar *compile-file-warnings-behaviour
* :warn
)
121 (defvar *compile-file-failure-behaviour
* #+sbcl
:error
#-sbcl
:warn
)
123 (defvar *verbose-out
* nil
)
125 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
128 (defmacro aif
(test then
&optional else
)
129 `(let ((it ,test
)) (if it
,then
,else
)))
131 (defun pathname-sans-name+type
(pathname)
132 "Returns a new pathname with same HOST, DEVICE, DIRECTORY as PATHNAME,
133 and NIL NAME and TYPE components"
134 (make-pathname :name nil
:type nil
:defaults pathname
))
136 (define-modify-macro appendf
(&rest args
)
137 append
"Append onto list")
139 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
140 ;; classes, condiitons
142 (define-condition system-definition-error
(error) ()
143 ;; [this use of :report should be redundant, but unfortunately it's not.
144 ;; cmucl's lisp::output-instance prefers the kernel:slot-class-print-function
145 ;; over print-object; this is always conditions::%print-condition for
146 ;; condition objects, which in turn does inheritance of :report options at
147 ;; run-time. fortunately, inheritance means we only need this kludge here in
148 ;; order to fix all conditions that build on it. -- rgr, 28-Jul-02.]
149 #+cmu
(:report print-object
))
151 (define-condition formatted-system-definition-error
(system-definition-error)
152 ((format-control :initarg
:format-control
:reader format-control
)
153 (format-arguments :initarg
:format-arguments
:reader format-arguments
))
154 (:report
(lambda (c s
)
155 (apply #'format s
(format-control c
) (format-arguments c
)))))
157 (define-condition circular-dependency
(system-definition-error)
158 ((components :initarg
:components
:reader circular-dependency-components
)))
160 (define-condition missing-component
(system-definition-error)
161 ((requires :initform
"(unnamed)" :reader missing-requires
:initarg
:requires
)
162 (version :initform nil
:reader missing-version
:initarg
:version
)
163 (parent :initform nil
:reader missing-parent
:initarg
:parent
)))
165 (define-condition missing-dependency
(missing-component)
166 ((required-by :initarg
:required-by
:reader missing-required-by
)))
168 (define-condition operation-error
(error)
169 ((component :reader error-component
:initarg
:component
)
170 (operation :reader error-operation
:initarg
:operation
))
171 (:report
(lambda (c s
)
172 (format s
"~@<erred while invoking ~A on ~A~@:>"
173 (error-operation c
) (error-component c
)))))
174 (define-condition compile-error
(operation-error) ())
175 (define-condition compile-failed
(compile-error) ())
176 (define-condition compile-warned
(compile-error) ())
178 (defclass component
()
179 ((name :accessor component-name
:initarg
:name
:documentation
180 "Component name: designator for a string composed of portable pathname characters")
181 (version :accessor component-version
:initarg
:version
)
182 (in-order-to :initform nil
:initarg
:in-order-to
)
184 (do-first :initform nil
:initarg
:do-first
)
185 ;; methods defined using the "inline" style inside a defsystem form:
186 ;; need to store them somewhere so we can delete them when the system
188 (inline-methods :accessor component-inline-methods
:initform nil
)
189 (parent :initarg
:parent
:initform nil
:reader component-parent
)
190 ;; no direct accessor for pathname, we do this as a method to allow
191 ;; it to default in funky ways if not supplied
192 (relative-pathname :initarg
:pathname
)
193 (operation-times :initform
(make-hash-table )
194 :accessor component-operation-times
)
195 ;; XXX we should provide some atomic interface for updating the
196 ;; component properties
197 (properties :accessor component-properties
:initarg
:properties
200 ;;;; methods: conditions
202 (defmethod print-object ((c missing-dependency
) s
)
203 (format s
"~@<~A, required by ~A~@:>"
204 (call-next-method c nil
) (missing-required-by c
)))
206 (defun sysdef-error (format &rest arguments
)
207 (error 'formatted-system-definition-error
:format-control format
:format-arguments arguments
))
209 ;;;; methods: components
211 (defmethod print-object ((c missing-component
) s
)
212 (format s
"~@<component ~S not found~
213 ~@[ or does not match version ~A~]~
217 (when (missing-parent c
)
218 (component-name (missing-parent c
)))))
220 (defgeneric component-system
(component)
221 (:documentation
"Find the top-level system containing COMPONENT"))
223 (defmethod component-system ((component component
))
224 (aif (component-parent component
)
225 (component-system it
)
228 (defmethod print-object ((c component
) stream
)
229 (print-unreadable-object (c stream
:type t
:identity t
)
231 (prin1 (component-name c
) stream
))))
233 (defclass module
(component)
234 ((components :initform nil
:accessor module-components
:initarg
:components
)
235 ;; what to do if we can't satisfy a dependency of one of this module's
236 ;; components. This allows a limited form of conditional processing
237 (if-component-dep-fails :initform
:fail
238 :accessor module-if-component-dep-fails
239 :initarg
:if-component-dep-fails
)
240 (default-component-class :accessor module-default-component-class
241 :initform
'cl-source-file
:initarg
:default-component-class
)))
243 (defgeneric component-pathname
(component)
244 (:documentation
"Extracts the pathname applicable for a particular component."))
246 (defun component-parent-pathname (component)
247 (aif (component-parent component
)
248 (component-pathname it
)
249 *default-pathname-defaults
*))
251 (defgeneric component-relative-pathname
(component)
252 (:documentation
"Extracts the relative pathname applicable for a particular component."))
254 (defmethod component-relative-pathname ((component module
))
255 (or (slot-value component
'relative-pathname
)
257 :directory
`(:relative
,(component-name component
))
258 :host
(pathname-host (component-parent-pathname component
)))))
260 (defmethod component-pathname ((component component
))
261 (let ((*default-pathname-defaults
* (component-parent-pathname component
)))
262 (merge-pathnames (component-relative-pathname component
))))
264 (defgeneric component-property
(component property
))
266 (defmethod component-property ((c component
) property
)
267 (cdr (assoc property
(slot-value c
'properties
) :test
#'equal
)))
269 (defgeneric (setf component-property
) (new-value component property
))
271 (defmethod (setf component-property
) (new-value (c component
) property
)
272 (let ((a (assoc property
(slot-value c
'properties
) :test
#'equal
)))
274 (setf (cdr a
) new-value
)
275 (setf (slot-value c
'properties
)
276 (acons property new-value
(slot-value c
'properties
))))))
278 (defclass system
(module)
279 ((description :accessor system-description
:initarg
:description
)
281 :accessor system-long-description
:initarg
:long-description
)
282 (author :accessor system-author
:initarg
:author
)
283 (maintainer :accessor system-maintainer
:initarg
:maintainer
)
284 (licence :accessor system-licence
:initarg
:licence
)))
286 ;;; version-satisfies
288 ;;; with apologies to christophe rhodes ...
289 (defun split (string &optional max
(ws '(#\Space
#\Tab
)))
290 (flet ((is-ws (char) (find char ws
)))
292 (let ((list nil
) (start 0) (words 0) end
)
294 (when (and max
(>= words
(1- max
)))
295 (return (cons (subseq string start
) list
)))
296 (setf end
(position-if #'is-ws string
:start start
))
297 (push (subseq string start end
) list
)
299 (unless end
(return list
))
300 (setf start
(1+ end
)))))))
302 (defgeneric version-satisfies
(component version
))
304 (defmethod version-satisfies ((c component
) version
)
305 (unless (and version
(slot-boundp c
'version
))
306 (return-from version-satisfies t
))
307 (let ((x (mapcar #'parse-integer
308 (split (component-version c
) nil
'(#\.
))))
309 (y (mapcar #'parse-integer
310 (split version nil
'(#\.
)))))
311 (labels ((bigger (x y
)
314 ((> (car x
) (car y
)) t
)
316 (bigger (cdr x
) (cdr y
))))))
317 (and (= (car x
) (car y
))
318 (or (not (cdr y
)) (bigger (cdr x
) (cdr y
)))))))
320 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
323 (defvar *defined-systems
* (make-hash-table :test
'equal
))
324 (defun coerce-name (name)
326 (component (component-name name
))
327 (symbol (string-downcase (symbol-name name
)))
329 (t (sysdef-error "~@<invalid component designator ~A~@:>" name
))))
331 ;;; for the sake of keeping things reasonably neat, we adopt a
332 ;;; convention that functions in this list are prefixed SYSDEF-
334 (defvar *system-definition-search-functions
*
335 '(sysdef-central-registry-search))
337 (defun system-definition-pathname (system)
338 (some (lambda (x) (funcall x system
))
339 *system-definition-search-functions
*))
341 (defvar *central-registry
*
342 '(*default-pathname-defaults
*
343 #+nil
"/home/dan/src/sourceforge/cclan/asdf/systems/"
344 #+nil
"telent:asdf;systems;"))
346 (defun sysdef-central-registry-search (system)
347 (let ((name (coerce-name system
)))
349 (dolist (dir *central-registry
*)
350 (let* ((defaults (eval dir
))
353 :defaults defaults
:version
:newest
354 :name name
:type
"asd" :case
:local
))))
355 (if (and file
(probe-file file
))
359 (defun find-system (name &optional
(error-p t
))
360 (let* ((name (coerce-name name
))
361 (in-memory (gethash name
*defined-systems
*))
362 (on-disk (system-definition-pathname name
)))
365 (< (car in-memory
) (file-write-date on-disk
))))
366 (let ((*package
* (make-package (gensym #.
(package-name *package
*))
368 (format *verbose-out
*
369 "~&~@<; ~@;loading system definition from ~A into ~A~@:>~%"
370 ;; FIXME: This wants to be (ENOUGH-NAMESTRING
371 ;; ON-DISK), but CMUCL barfs on that.
375 (let ((in-memory (gethash name
*defined-systems
*)))
377 (progn (if on-disk
(setf (car in-memory
) (file-write-date on-disk
)))
379 (if error-p
(error 'missing-component
:requires name
))))))
381 (defun register-system (name system
)
382 (format *verbose-out
* "~&~@<; ~@;registering ~A as ~A~@:>~%" system name
)
383 (setf (gethash (coerce-name name
) *defined-systems
*)
384 (cons (get-universal-time) system
)))
386 (defun system-registered-p (name)
387 (gethash (coerce-name name
) *defined-systems
*))
389 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
390 ;;; finding components
392 (defgeneric find-component
(module name
&optional version
)
393 (:documentation
"Finds the component with name NAME present in the
394 MODULE module; if MODULE is nil, then the component is assumed to be a
397 (defmethod find-component ((module module
) name
&optional version
)
398 (if (slot-boundp module
'components
)
399 (let ((m (find name
(module-components module
)
400 :test
#'equal
:key
#'component-name
)))
401 (if (and m
(version-satisfies m version
)) m
))))
404 ;;; a component with no parent is a system
405 (defmethod find-component ((module (eql nil
)) name
&optional version
)
406 (let ((m (find-system name nil
)))
407 (if (and m
(version-satisfies m version
)) m
)))
409 ;;; component subclasses
411 (defclass source-file
(component) ())
413 (defclass cl-source-file
(source-file) ())
414 (defclass c-source-file
(source-file) ())
415 (defclass java-source-file
(source-file) ())
416 (defclass static-file
(source-file) ())
417 (defclass doc-file
(static-file) ())
418 (defclass html-file
(doc-file) ())
420 (defgeneric source-file-type
(component system
))
421 (defmethod source-file-type ((c cl-source-file
) (s module
)) "lisp")
422 (defmethod source-file-type ((c c-source-file
) (s module
)) "c")
423 (defmethod source-file-type ((c java-source-file
) (s module
)) "java")
424 (defmethod source-file-type ((c html-file
) (s module
)) "html")
425 (defmethod source-file-type ((c static-file
) (s module
)) nil
)
427 (defmethod component-relative-pathname ((component source-file
))
428 (let* ((*default-pathname-defaults
* (component-parent-pathname component
))
431 :name
(component-name component
)
432 :type
(source-file-type component
433 (component-system component
)))))
434 (if (slot-value component
'relative-pathname
)
436 (slot-value component
'relative-pathname
)
440 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
443 ;;; one of these is instantiated whenever (operate ) is called
445 (defclass operation
()
446 ((forced :initform nil
:initarg
:force
:accessor operation-forced
)
447 (original-initargs :initform nil
:initarg
:original-initargs
448 :accessor operation-original-initargs
)
449 (visited-nodes :initform nil
:accessor operation-visited-nodes
)
450 (visiting-nodes :initform nil
:accessor operation-visiting-nodes
)
451 (parent :initform nil
:initarg
:parent
:accessor operation-parent
)))
453 (defmethod print-object ((o operation
) stream
)
454 (print-unreadable-object (o stream
:type t
:identity t
)
456 (prin1 (operation-original-initargs o
) stream
))))
458 (defmethod shared-initialize :after
((operation operation
) slot-names
461 (declare (ignore slot-names force
))
462 ;; empty method to disable initarg validity checking
465 (defgeneric perform
(operation component
))
466 (defgeneric operation-done-p
(operation component
))
467 (defgeneric explain
(operation component
))
468 (defgeneric output-files
(operation component
))
469 (defgeneric input-files
(operation component
))
471 (defun node-for (o c
)
472 (cons (class-name (class-of o
)) c
))
474 (defgeneric operation-ancestor
(operation)
475 (:documentation
"Recursively chase the operation's parent pointer until we get to the head of the tree"))
477 (defmethod operation-ancestor ((operation operation
))
478 (aif (operation-parent operation
)
479 (operation-ancestor it
)
483 (defun make-sub-operation (c o dep-c dep-o
)
484 (let* ((args (copy-list (operation-original-initargs o
)))
485 (force-p (getf args
:force
)))
486 ;; note explicit comparison with T: any other non-NIL force value
487 ;; (e.g. :recursive) will pass through
488 (cond ((and (null (component-parent c
))
489 (null (component-parent dep-c
))
491 (when (eql force-p t
)
492 (setf (getf args
:force
) nil
))
493 (apply #'make-instance dep-o
495 :original-initargs args args
))
496 ((subtypep (type-of o
) dep-o
)
499 (apply #'make-instance dep-o
500 :parent o
:original-initargs args args
)))))
503 (defgeneric visit-component
(operation component data
))
505 (defmethod visit-component ((o operation
) (c component
) data
)
506 (unless (component-visited-p o c
)
507 (push (cons (node-for o c
) data
)
508 (operation-visited-nodes (operation-ancestor o
)))))
510 (defgeneric component-visited-p
(operation component
))
512 (defmethod component-visited-p ((o operation
) (c component
))
513 (assoc (node-for o c
)
514 (operation-visited-nodes (operation-ancestor o
))
517 (defgeneric (setf visiting-component
) (new-value operation component
))
519 (defmethod (setf visiting-component
) (new-value operation component
)
520 ;; MCL complains about unused lexical variables
521 (declare (ignorable new-value operation component
)))
523 (defmethod (setf visiting-component
) (new-value (o operation
) (c component
))
524 (let ((node (node-for o c
))
525 (a (operation-ancestor o
)))
527 (pushnew node
(operation-visiting-nodes a
) :test
'equal
)
528 (setf (operation-visiting-nodes a
)
529 (remove node
(operation-visiting-nodes a
) :test
'equal
)))))
531 (defgeneric component-visiting-p
(operation component
))
533 (defmethod component-visiting-p ((o operation
) (c component
))
534 (let ((node (cons o c
)))
535 (member node
(operation-visiting-nodes (operation-ancestor o
))
538 (defgeneric component-depends-on
(operation component
))
540 (defmethod component-depends-on ((o operation
) (c component
))
541 (cdr (assoc (class-name (class-of o
))
542 (slot-value c
'in-order-to
))))
544 (defgeneric component-self-dependencies
(operation component
))
546 (defmethod component-self-dependencies ((o operation
) (c component
))
547 (let ((all-deps (component-depends-on o c
)))
548 (remove-if-not (lambda (x)
549 (member (component-name c
) (cdr x
) :test
#'string
=))
552 (defmethod input-files ((operation operation
) (c component
))
553 (let ((parent (component-parent c
))
554 (self-deps (component-self-dependencies operation c
)))
556 (mapcan (lambda (dep)
557 (destructuring-bind (op name
) dep
558 (output-files (make-instance op
)
559 (find-component parent name
))))
561 ;; no previous operations needed? I guess we work with the
562 ;; original source file, then
563 (list (component-pathname c
)))))
565 (defmethod input-files ((operation operation
) (c module
)) nil
)
567 (defmethod operation-done-p ((o operation
) (c component
))
568 (let ((out-files (output-files o c
))
569 (in-files (input-files o c
)))
570 (cond ((and (not in-files
) (not out-files
))
571 ;; arbitrary decision: an operation that uses nothing to
572 ;; produce nothing probably isn't doing much
577 (component-operation-times c
))))
581 (mapcar #'file-write-date in-files
)) 0)))))
585 (every #'probe-file out-files
)
586 (> (apply #'min
(mapcar #'file-write-date out-files
))
587 (apply #'max
(mapcar #'file-write-date in-files
)) ))))))
589 ;;; So you look at this code and think "why isn't it a bunch of
590 ;;; methods". And the answer is, because standard method combination
591 ;;; runs :before methods most->least-specific, which is back to front
592 ;;; for our purposes. And CLISP doesn't have non-standard method
593 ;;; combinations, so let's keep it simple and aspire to portability
595 (defgeneric traverse
(operation component
))
596 (defmethod traverse ((operation operation
) (c component
))
598 (labels ((do-one-dep (required-op required-c required-v
)
599 (let* ((dep-c (or (find-component
601 ;; XXX tacky. really we should build the
602 ;; in-order-to slot with canonicalized
603 ;; names instead of coercing this late
604 (coerce-name required-c
) required-v
)
605 (error 'missing-dependency
:required-by c
607 :requires required-c
)))
608 (op (make-sub-operation c operation dep-c required-op
)))
609 (traverse op dep-c
)))
611 (cond ((eq op
'feature
)
612 (or (member (car dep
) *features
*)
613 (error 'missing-dependency
:required-by c
614 :requires
(car dep
) :version nil
)))
618 (assert (string-equal
619 (symbol-name (first d
))
622 (do-one-dep op
(second d
) (third d
))))
624 (appendf forced
(do-one-dep op d nil
)))))))))
625 (aif (component-visited-p operation c
)
626 (return-from traverse
627 (if (cdr it
) (list (cons 'pruned-op c
)) nil
)))
629 (if (component-visiting-p operation c
)
630 (error 'circular-dependency
:components
(list c
)))
631 (setf (visiting-component operation c
) t
)
632 (loop for
(required-op . deps
) in
(component-depends-on operation c
)
633 do
(do-dep required-op deps
))
636 (when (typep c
'module
)
637 (let ((at-least-one nil
)
640 (loop for kid in
(module-components c
)
642 (appendf forced
(traverse operation kid
))
643 (missing-dependency (condition)
644 (if (eq (module-if-component-dep-fails c
) :fail
)
646 (setf error condition
))
649 (setf at-least-one t
))))
650 (when (and (eq (module-if-component-dep-fails c
) :try-next
)
654 ;; now the thing itself
655 (when (or forced module-ops
656 (not (operation-done-p operation c
))
657 (let ((f (operation-forced (operation-ancestor operation
))))
658 (and f
(or (not (consp f
))
659 (member (component-name
660 (operation-ancestor operation
))
661 (mapcar #'coerce-name f
)
663 (let ((do-first (cdr (assoc (class-name (class-of operation
))
664 (slot-value c
'do-first
)))))
665 (loop for
(required-op . deps
) in do-first
666 do
(do-dep required-op deps
)))
667 (setf forced
(append (delete 'pruned-op forced
:key
#'car
)
668 (delete 'pruned-op module-ops
:key
#'car
)
669 (list (cons operation c
))))))
670 (setf (visiting-component operation c
) nil
)
671 (visit-component operation c
(and forced t
))
675 (defmethod perform ((operation operation
) (c source-file
))
677 "~@<required method PERFORM not implemented ~
678 for operation ~A, component ~A~@:>"
679 (class-of operation
) (class-of c
)))
681 (defmethod perform ((operation operation
) (c module
))
684 (defmethod explain ((operation operation
) (component component
))
685 (format *verbose-out
* "~&;;; ~A on ~A~%" operation component
))
689 (defclass compile-op
(operation)
690 ((proclamations :initarg
:proclamations
:accessor compile-op-proclamations
:initform nil
)
691 (on-warnings :initarg
:on-warnings
:accessor operation-on-warnings
692 :initform
*compile-file-warnings-behaviour
*)
693 (on-failure :initarg
:on-failure
:accessor operation-on-failure
694 :initform
*compile-file-failure-behaviour
*)))
696 (defmethod perform :before
((operation compile-op
) (c source-file
))
697 (map nil
#'ensure-directories-exist
(output-files operation c
)))
699 (defmethod perform :after
((operation operation
) (c component
))
700 (setf (gethash (type-of operation
) (component-operation-times c
))
701 (get-universal-time)))
703 ;;; perform is required to check output-files to find out where to put
704 ;;; its answers, in case it has been overridden for site policy
705 (defmethod perform ((operation compile-op
) (c cl-source-file
))
706 #-
:broken-fasl-loader
707 (let ((source-file (component-pathname c
))
708 (output-file (car (output-files operation c
))))
709 (multiple-value-bind (output warnings-p failure-p
)
710 (compile-file source-file
711 :output-file output-file
)
712 ;(declare (ignore output))
714 (case (operation-on-warnings operation
)
716 "~@<COMPILE-FILE warned while performing ~A on ~A.~@:>"
718 (:error
(error 'compile-warned
:component c
:operation operation
))
721 (case (operation-on-failure operation
)
723 "~@<COMPILE-FILE failed while performing ~A on ~A.~@:>"
725 (:error
(error 'compile-failed
:component c
:operation operation
))
728 (error 'compile-error
:component c
:operation operation
)))))
730 (defmethod output-files ((operation compile-op
) (c cl-source-file
))
731 #-
:broken-fasl-loader
(list (compile-file-pathname (component-pathname c
)))
732 #+:broken-fasl-loader
(list (component-pathname c
)))
734 (defmethod perform ((operation compile-op
) (c static-file
))
737 (defmethod output-files ((operation compile-op
) (c static-file
))
742 (defclass load-op
(operation) ())
744 (defmethod perform ((o load-op
) (c cl-source-file
))
745 (mapcar #'load
(input-files o c
)))
747 (defmethod perform ((operation load-op
) (c static-file
))
749 (defmethod operation-done-p ((operation load-op
) (c static-file
))
752 (defmethod output-files ((o operation
) (c component
))
755 (defmethod component-depends-on ((operation load-op
) (c component
))
756 (cons (list 'compile-op
(component-name c
))
761 (defclass load-source-op
(operation) ())
763 (defmethod perform ((o load-source-op
) (c cl-source-file
))
764 (let ((source (component-pathname c
)))
765 (setf (component-property c
'last-loaded-as-source
)
767 (get-universal-time)))))
769 (defmethod perform ((operation load-source-op
) (c static-file
))
772 (defmethod output-files ((operation load-source-op
) (c component
))
775 ;;; FIXME: we simply copy load-op's dependencies. this is Just Not Right.
776 (defmethod component-depends-on ((o load-source-op
) (c component
))
777 (let ((what-would-load-op-do (cdr (assoc 'load-op
778 (slot-value c
'in-order-to
)))))
779 (mapcar (lambda (dep)
780 (if (eq (car dep
) 'load-op
)
781 (cons 'load-source-op
(cdr dep
))
783 what-would-load-op-do
)))
785 (defmethod operation-done-p ((o load-source-op
) (c source-file
))
786 (if (or (not (component-property c
'last-loaded-as-source
))
787 (> (file-write-date (component-pathname c
))
788 (component-property c
'last-loaded-as-source
)))
791 (defclass test-op
(operation) ())
793 (defmethod perform ((operation test-op
) (c component
))
796 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
797 ;;; invoking operations
799 (defun operate (operation-class system
&rest args
)
800 (let* ((op (apply #'make-instance operation-class
801 :original-initargs args args
))
803 (if (getf args
:verbose t
)
805 (make-broadcast-stream)))
806 (system (if (typep system
'component
) system
(find-system system
)))
807 (steps (traverse op system
)))
808 (with-compilation-unit ()
809 (loop for
(op . component
) in steps do
812 (progn (perform op component
)
817 (format s
"~@<Retry performing ~S on ~S.~@:>"
823 "~@<Continue, treating ~S on ~S as ~
824 having been successful.~@:>"
826 (setf (gethash (type-of op
)
827 (component-operation-times component
))
828 (get-universal-time))
831 (defun oos (&rest args
)
832 "Alias of OPERATE function"
833 (apply #'operate args
))
835 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
838 (defun remove-keyword (key arglist
)
839 (labels ((aux (key arglist
)
840 (cond ((null arglist
) nil
)
841 ((eq key
(car arglist
)) (cddr arglist
))
842 (t (cons (car arglist
) (cons (cadr arglist
)
844 key
(cddr arglist
))))))))
847 (defmacro defsystem
(name &body options
)
848 (destructuring-bind (&key pathname
(class 'system
) &allow-other-keys
) options
849 (let ((component-options (remove-keyword :class options
)))
851 ;; system must be registered before we parse the body, otherwise
852 ;; we recur when trying to find an existing system of the same name
853 ;; to reuse options (e.g. pathname) from
854 (let ((s (system-registered-p ',name
)))
855 (cond ((and s
(eq (type-of (cdr s
)) ',class
))
856 (setf (car s
) (get-universal-time)))
859 (sysdef-error "Cannot redefine the existing system ~A with a different class" s
)
861 (change-class (cdr s
) ',class
))
863 (register-system (quote ,name
)
864 (make-instance ',class
:name
',name
)))))
865 (parse-component-form nil
(apply
867 :module
(coerce-name ',name
)
870 (pathname-sans-name+type
871 (resolve-symlinks *load-truename
*))
872 *default-pathname-defaults
*)
873 ',component-options
))))))
876 (defun class-for-type (parent type
)
879 (or (find-symbol (symbol-name type
) *package
*)
880 (find-symbol (symbol-name type
) #.
(package-name *package
*)))
884 (or (module-default-component-class parent
)
885 (find-class 'cl-source-file
)))
886 (sysdef-error "~@<don't recognize component type ~A~@:>" type
))))
888 (defun maybe-add-tree (tree op1 op2 c
)
889 "Add the node C at /OP1/OP2 in TREE, unless it's there already.
890 Returns the new tree (which probably shares structure with the old one)"
891 (let ((first-op-tree (assoc op1 tree
)))
894 (aif (assoc op2
(cdr first-op-tree
))
895 (if (find c
(cdr it
))
897 (setf (cdr it
) (cons c
(cdr it
))))
898 (setf (cdr first-op-tree
)
899 (acons op2
(list c
) (cdr first-op-tree
))))
901 (acons op1
(list (list op2 c
)) tree
))))
903 (defun union-of-dependencies (&rest deps
)
904 (let ((new-tree nil
))
906 (dolist (op-tree dep
)
907 (dolist (op (cdr op-tree
))
910 (maybe-add-tree new-tree
(car op-tree
) (car op
) c
))))))
914 (defun remove-keys (key-names args
)
915 (loop for
( name val
) on args by
#'cddr
916 unless
(member (symbol-name name
) key-names
917 :key
#'symbol-name
:test
'equal
)
918 append
(list name val
)))
920 (defvar *serial-depends-on
*)
922 (defun parse-component-form (parent options
)
924 (type name
&rest rest
&key
925 ;; the following list of keywords is reproduced below in the
926 ;; remove-keys form. important to keep them in sync
927 components pathname default-component-class
928 perform explain output-files operation-done-p
929 depends-on serial in-order-to
931 &allow-other-keys
) options
932 (check-component-input type name depends-on components in-order-to
)
933 (let* ((other-args (remove-keys
934 '(components pathname default-component-class
935 perform explain output-files operation-done-p
936 depends-on serial in-order-to
)
939 (or (find-component parent name
)
940 (make-instance (class-for-type parent type
)))))
941 (when (boundp '*serial-depends-on
*)
943 (concatenate 'list
*serial-depends-on
* depends-on
)))
944 (apply #'reinitialize-instance
946 :name
(coerce-name name
)
950 (when (typep ret
'module
)
951 (setf (module-default-component-class ret
)
952 (or default-component-class
953 (and (typep parent
'module
)
954 (module-default-component-class parent
))))
955 (let ((*serial-depends-on
* nil
))
956 (setf (module-components ret
)
957 (loop for c-form in components
958 for c
= (parse-component-form ret c-form
)
961 do
(push (component-name c
) *serial-depends-on
*)))))
963 (setf (slot-value ret
'in-order-to
)
964 (union-of-dependencies
966 `((compile-op (compile-op ,@depends-on
))
967 (load-op (load-op ,@depends-on
))))
968 (slot-value ret
'do-first
) `((compile-op (load-op ,@depends-on
))))
970 (loop for
(n v
) in
`((perform ,perform
) (explain ,explain
)
971 (output-files ,output-files
)
972 (operation-done-p ,operation-done-p
))
974 ;; this is inefficient as most of the stored
975 ;; methods will not be for this particular gf n
976 ;; But this is hardly performance-critical
977 (lambda (m) (remove-method (symbol-function n
) m
))
978 (component-inline-methods ret
))
980 do
(destructuring-bind (op qual
(o c
) &body body
) v
982 (eval `(defmethod ,n
,qual
((,o
,op
) (,c
(eql ,ret
)))
984 (component-inline-methods ret
))))
987 (defun check-component-input (type name depends-on components in-order-to
)
988 "A partial test of the values of a component."
989 (unless (listp depends-on
)
990 (sysdef-error-component ":depends-on must be a list."
991 type name depends-on
))
992 (unless (listp components
)
993 (sysdef-error-component ":components must be NIL or a list of components."
994 type name components
))
995 (unless (and (listp in-order-to
) (listp (car in-order-to
)))
996 (sysdef-error-component ":in-order-to must be NIL or a list of components."
997 type name in-order-to
)))
999 (defun sysdef-error-component (msg type name value
)
1000 (sysdef-error (concatenate 'string msg
1001 "~&The value specified for ~(~A~) ~A is ~W")
1004 (defun resolve-symlinks (path)
1005 #-allegro
(truename path
)
1006 #+allegro
(excl:pathname-resolve-symbolic-links path
)
1011 ;;; run-shell-command functions for other lisp implementations will be
1012 ;;; gratefully accepted, if they do the same thing. If the docstring
1013 ;;; is ambiguous, send a bug report
1015 (defun run-shell-command (control-string &rest args
)
1016 "Interpolate ARGS into CONTROL-STRING as if by FORMAT, and
1017 synchronously execute the result using a Bourne-compatible shell, with
1018 output to *verbose-out*. Returns the shell's exit code."
1019 (let ((command (apply #'format nil control-string args
)))
1020 (format *verbose-out
* "; $ ~A~%" command
)
1022 (sb-impl::process-exit-code
1026 :input nil
:output
*verbose-out
*))
1029 (ext:process-exit-code
1033 :input nil
:output
*verbose-out
*))
1036 (excl:run-shell-command command
:input nil
:output
*verbose-out
*)
1039 (system:call-system-showing-output
1041 :shell-type
"/bin/sh"
1042 :output-stream
*verbose-out
*)
1044 #+clisp
;XXX not exactly *verbose-out*, I know
1045 (ext:run-shell-command command
:output
:terminal
:wait t
)
1049 (ccl:external-process-status
1050 (ccl:run-program
"/bin/sh" (list "-c" command
)
1051 :input nil
:output
*verbose-out
*
1053 #+ecl
;; courtesy of Juan Jose Garcia Ripoll
1055 #-
(or openmcl clisp lispworks allegro scl cmu sbcl ecl
)
1056 (error "RUN-SHELL-PROGRAM not implemented for this Lisp")
1060 (defgeneric hyperdocumentation
(package name doc-type
))
1061 (defmethod hyperdocumentation ((package symbol
) name doc-type
)
1062 (hyperdocumentation (find-package package
) name doc-type
))
1064 (defun hyperdoc (name doc-type
)
1065 (hyperdocumentation (symbol-package name
) name doc-type
))
1068 (pushnew :asdf
*features
*)
1071 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
1072 (when (sb-ext:posix-getenv
"SBCL_BUILDING_CONTRIB")
1073 (pushnew :sbcl-hooks-require
*features
*)))
1075 #+(and sbcl sbcl-hooks-require
)
1077 (defun module-provide-asdf (name)
1078 (handler-bind ((style-warning #'muffle-warning
))
1079 (let* ((*verbose-out
* (make-broadcast-stream))
1080 (system (asdf:find-system name nil
)))
1082 (asdf:operate
'asdf
:load-op name
)
1086 '(merge-pathnames "systems/"
1087 (truename (sb-ext:posix-getenv
"SBCL_HOME")))
1091 '(merge-pathnames "site-systems/"
1092 (truename (sb-ext:posix-getenv
"SBCL_HOME")))
1096 '(merge-pathnames ".sbcl/systems/"
1097 (user-homedir-pathname))
1100 (pushnew 'module-provide-asdf sb-ext
:*module-provider-functions
*))