| 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 |
|---|