forcing.lisp 6.77 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
;;;; -------------------------------------------------------------------------
;;;; Forcing

(uiop/package:define-package :asdf/forcing
  (:recycle :asdf/forcing :asdf/plan :asdf)
  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session
        :asdf/component :asdf/operation :asdf/system :asdf/system-registry)
  (:export
   #:forcing #:make-forcing #:forced #:forced-not #:performable-p
   #:normalize-forced-systems #:normalize-forced-not-systems
   #:action-forced-p #:action-forced-not-p))
(in-package :asdf/forcing)

;;;; Forcing
(with-upgradability ()
  (defclass forcing ()
    (;; Can plans using this forcing be PERFORMed? A plan that has different force and force-not
     ;; settings than the session can only be used for read-only queries that do not cause the
     ;; status of any action to be raised.
     (performable-p :initform nil :initarg :performable-p :reader performable-p)
     ;; Parameters
     (parameters :initform nil :initarg :parameters :reader parameters)
     ;; Table of systems specified via :force arguments
     (forced :initarg :forced :reader forced)
     ;; Table of systems specified via :force-not argument (and/or immutable)
     (forced-not :initarg :forced-not :reader forced-not)))

  (defgeneric action-forced-p (forcing operation component)
    (:documentation "Is this action forced to happen in this plan?"))
  (defgeneric action-forced-not-p (forcing operation component)
    (:documentation "Is this action forced to not happen in this plan?
Takes precedence over action-forced-p."))

  (defun normalize-forced-systems (force system)
    "Given a SYSTEM on which operate is called and the specified FORCE argument,
extract a hash-set of systems that are forced, or a predicate on system names,
or NIL if none are forced, or :ALL if all are."
    (etypecase force
      ((or (member nil :all) hash-table function) force)
      (cons (list-to-hash-set (mapcar #'coerce-name force)))
      ((eql t) (when system (list-to-hash-set (list (coerce-name system)))))))

  (defun normalize-forced-not-systems (force-not system)
    "Given a SYSTEM on which operate is called, the specified FORCE-NOT argument,
and the set of IMMUTABLE systems, extract a hash-set of systems that are effectively forced-not,
or predicate on system names, or NIL if none are forced, or :ALL if all are."
    (let ((requested
            (etypecase force-not
              ((or (member nil :all) hash-table function) force-not)
              (cons (list-to-hash-set (mapcar #'coerce-name force-not)))
              ((eql t) (if system (let ((name (coerce-name system)))
                                    #'(lambda (x) (not (equal x name))))
                           :all)))))
      (if (and *immutable-systems* requested)
          #'(lambda (x) (or (call-function requested x)
                            (call-function *immutable-systems* x)))
          (or *immutable-systems* requested))))

  ;; TODO: shouldn't we be looking up the primary system name, rather than the system name?
  (defun action-override-p (forcing operation component override-accessor)
    "Given a plan, an action, and a function that given the plan accesses a set of overrides,
i.e. force or force-not, see if the override applies to the current action."
    (declare (ignore operation))
    (call-function (funcall override-accessor forcing)
                   (coerce-name (component-system (find-component () component)))))

  (defmethod action-forced-p (forcing operation component)
    (and
     ;; Did the user ask us to re-perform the action?
     (action-override-p forcing operation component 'forced)
     ;; You really can't force a builtin system and :all doesn't apply to it.
     (not (builtin-system-p (component-system component)))))

  (defmethod action-forced-not-p (forcing operation component)
    ;; Did the user ask us to not re-perform the action?
    ;; NB: force-not takes precedence over force, as it should
    (action-override-p forcing operation component 'forced-not))

  ;; Null forcing means no forcing either way
  (defmethod action-forced-p ((forcing null) (operation operation) (component component))
    nil)
  (defmethod action-forced-not-p ((forcing null) (operation operation) (component component))
    nil)

  (defun or-function (fun1 fun2)
    (cond
      ((or (null fun2) (eq fun1 :all)) fun1)
      ((or (null fun1) (eq fun2 :all)) fun2)
      (t #'(lambda (x) (or (call-function fun1 x) (call-function fun2 x))))))

  (defun make-forcing (&key performable-p system
                         (force nil force-p) (force-not nil force-not-p) &allow-other-keys)
    (let* ((session-forcing (when *asdf-session* (forcing *asdf-session*)))
           (system (and system (coerce-name system)))
           (forced (normalize-forced-systems force system))
           (forced-not (normalize-forced-not-systems force-not system))
           (parameters `(,@(when force `(:force ,force))
                         ,@(when force-not `(:force-not ,force-not))
                         ,@(when (or (eq force t) (eq force-not t)) `(:system ,system))
                         ,@(when performable-p `(:performable-p t))))
           forcing)
      (cond
        ((not session-forcing)
         (setf forcing (make-instance 'forcing
                                      :performable-p performable-p :parameters parameters
                                      :forced forced :forced-not forced-not))
         (when (and performable-p *asdf-session*)
           (setf (forcing *asdf-session*) forcing)))
        (performable-p
         (when (and (not (equal parameters (parameters session-forcing)))
                    (or force-p force-not-p))
           (parameter-error "~*~S and ~S arguments not allowed in a nested call to ~3:*~S ~
unless identically to toplevel"
                            (find-symbol* :operate :asdf) :force :force-not))
         (setf forcing session-forcing))
        (t
         (setf forcing (make-instance 'forcing
                           ;; Combine force and force-not with values from the toplevel-plan
                           :parameters `(,@parameters :on-top-of ,(parameters session-forcing))
                           :forced (or-function (forced session-forcing) forced)
                           :forced-not (or-function (forced-not session-forcing) forced-not)))))
      forcing))

  (defmethod print-object ((forcing forcing) stream)
    (print-unreadable-object (forcing stream :type t)
      (format stream "~{~S~^ ~}" (parameters forcing))))

  ;; During upgrade, the *asdf-session* may legitimately be NIL, so we must handle that case.
  (defmethod forcing ((x null))
    (if-let (session (toplevel-asdf-session))
      (forcing session)
      (make-forcing :performable-p t)))

  ;; When performing a plan that is a list of actions, use the toplevel asdf sesssion forcing.
  (defmethod forcing ((x cons)) (forcing (toplevel-asdf-session))))