upgrade.lisp 9.45 KB
Newer Older
1 2 3 4
;;;; -------------------------------------------------------------------------
;;;; Handle upgrade as forward- and backward-compatibly as possible
;; See https://bugs.launchpad.net/asdf/+bug/485687

5
(uiop/package:define-package :asdf/upgrade
6
  (:recycle :asdf/upgrade :asdf)
7
  (:use :uiop/common-lisp :uiop)
8
  (:export
9
   #:asdf-version #:*previous-asdf-versions* #:*asdf-version*
10
   #:asdf-message #:*verbose-out*
11
   #:upgrading-p #:when-upgrading #:upgrade-asdf #:defparameter*
12
   #:*post-upgrade-cleanup-hook* #:*post-upgrade-restart-hook* #:cleanup-upgraded-asdf
13
   ;; There will be no symbol left behind!
14
   #:intern*)
15
  (:import-from :uiop/package #:intern* #:find-symbol*))
16 17 18 19
(in-package :asdf/upgrade)

;;; Special magic to detect if this is an upgrade

20
(with-upgradability ()
21 22 23 24 25
  (defun asdf-version ()
    "Exported interface to the version of ASDF currently installed. A string.
You can compare this string with e.g.: (ASDF:VERSION-SATISFIES (ASDF:ASDF-VERSION) \"3.4.5.67\")."
    (when (find-package :asdf)
      (or (symbol-value (find-symbol (string :*asdf-version*) :asdf))
26 27 28 29 30
          (let* ((revsym (find-symbol (string :*asdf-revision*) :asdf))
                 (rev (and revsym (boundp revsym) (symbol-value revsym))))
            (etypecase rev
              (string rev)
              (cons (format nil "~{~D~^.~}" rev))
31
              (null "1.0"))))))
32 33 34
  ;; This (private) variable contains a list of versions of previously loaded variants of ASDF,
  ;; from which ASDF was upgraded.
  ;; Important: define *p-a-v* /before/ *a-v* so that they initialize correctly.
35 36 37 38 39 40 41 42 43
  (defvar *previous-asdf-versions*
    (let ((previous (asdf-version)))
      (when previous
        ;; Punt on hard package upgrade: from ASDF1 or ASDF2
        (when (version< previous "2.27") ;; 2.27 is the first to have the :asdf3 feature.
          (let ((away (format nil "~A-~A" :asdf previous)))
            (rename-package :asdf away)
            (when *load-verbose*
              (format t "~&; Renamed old ~A package away to ~A~%" :asdf away)))))
44 45
      (list previous)))
  ;; This public variable will be bound shortly to the currently loaded version of ASDF.
46
  (defvar *asdf-version* nil)
47
  ;; We need to clear systems from versions older than the one in this (private) parameter:
48
  (defparameter *oldest-forward-compatible-asdf-version* "2.33") ;; 2.32.13 renames a slot in component.
49
  ;; Semi-private variable: a designator for a stream on which to output ASDF progress messages
50
  (defvar *verbose-out* nil)
51
  ;; Private function by which ASDF outputs progress messages and warning messages:
52
  (defun asdf-message (format-string &rest format-args)
53
    (when *verbose-out* (apply 'format *verbose-out* format-string format-args)))
54
  ;; Private hook for functions to run after ASDF has upgraded itself from an older variant:
55
  (defvar *post-upgrade-cleanup-hook* ())
56 57 58 59
  ;; Private hook for functions to run after ASDF is restarted, whether by starting a process
  ;; from a dumped image or after upgrading from an older variant:
  ;; TODO: understand what happened with that hook, why functions are registered on it but it is
  ;; never called anymore. This is a bug that should be fixed before next release (3.1.8)!
60
  (defvar *post-upgrade-restart-hook* ())
61 62
  ;; Private function to detect whether the current upgrade counts as an incompatible
  ;; data schema upgrade implying the need to drop data.
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
63 64 65
  (defun upgrading-p (&optional (oldest-compatible-version *oldest-forward-compatible-asdf-version*))
    (and *previous-asdf-versions*
         (version< (first *previous-asdf-versions*) oldest-compatible-version)))
66 67 68
  ;; Private variant of defparameter that works in presence of incompatible upgrades:
  ;; behaves like defvar in a compatible upgrade (e.g. reloading system after simple code change),
  ;; but behaves like defparameter if in presence of an incompatible upgrade.
69 70 71 72 73 74 75 76
  (defmacro defparameter* (var value &optional docstring (version *oldest-forward-compatible-asdf-version*))
    (let* ((name (string-trim "*" var))
           (valfun (intern (format nil "%~A-~A-~A" :compute name :value))))
      `(progn
         (defun ,valfun () ,value)
         (defvar ,var (,valfun) ,@(ensure-list docstring))
         (when (upgrading-p ,version)
           (setf ,var (,valfun))))))
77 78 79
  ;; Private macro to declare sections of code that are only compiled and run when upgrading.
  ;; The use of eval portably ensures that the code will not have adverse compile-time side-effects,
  ;; whereas the use of handler-bind portably ensures that it will not issue warnings when it runs.
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
80 81
  (defmacro when-upgrading ((&key (version *oldest-forward-compatible-asdf-version*)
                               (upgrading-p `(upgrading-p ,version)) when) &body body)
82
    "A wrapper macro for code that should only be run when upgrading a
83
previously-loaded version of ASDF."
84
    `(with-upgradability ()
85 86
       (when (and ,upgrading-p ,@(when when `(,when)))
         (handler-bind ((style-warning #'muffle-warning))
87
           (eval '(progn ,@body))))))
88
  ;; Only now can we safely update the version.
89
  (let* (;; For bug reporting sanity, please always bump this version when you modify this file.
90
         ;; Please also modify asdf.asd to reflect this change. make bump-version v=3.4.5.67.8
91
         ;; can help you do these changes in synch (look at the source for documentation).
92
         ;; Relying on its automation, the version is now redundantly present on top of asdf.lisp.
93 94
         ;; "3.4" would be the general branch for major version 3, minor version 4.
         ;; "3.4.5" would be an official release in the 3.4 branch.
95
         ;; "3.4.5.67" would be a development version in the official branch, on top of 3.4.5.
96 97
         ;; "3.4.5.0.8" would be your eighth local modification of official release 3.4.5
         ;; "3.4.5.67.8" would be your eighth local modification of development version 3.4.5.67
Robert Goldman's avatar
Robert Goldman committed
98
         (asdf-version "3.1.7.25")
99
         (existing-version (asdf-version)))
100
    (setf *asdf-version* asdf-version)
101 102
    (when (and existing-version (not (equal asdf-version existing-version)))
      (push existing-version *previous-asdf-versions*)
103 104
      (when (or *verbose-out* *load-verbose*)
        (format (or *verbose-out* *trace-output*)
105
                (compatfmt "~&~@<; ~@;Upgrading ASDF ~@[from version ~A ~]to version ~A~@:>~%")
106
                existing-version asdf-version)))))
107

108
;;; Upon upgrade, specially frob some functions and classes that are being incompatibly redefined
109 110
(when-upgrading ()
  (let ((redefined-functions ;; gf signature and/or semantics changed incompatibly. Oops.
111 112
          ;; NB: it's too late to do anything about functions in UIOP!
          ;; If you introduce some critically incompatibility there, you must change name.
113
          '()) ;; empty now that we don't unintern, but wholly punt on ASDF 2.26 or earlier.
114
        (redefined-classes
115
          ;; redefining the classes causes interim circularities
116
          ;; with the old ASDF during upgrade, and many implementations bork
117
          '((#:compile-concatenated-source-op (#:operation) ()))))
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
118
    (loop :for name :in redefined-functions
119 120
          :for sym = (find-symbol* name :asdf nil) :do
            (when sym
121
              ;; CLISP seemingly can't fmakunbound and define a function in the same fasl. Sigh.
122
              #-clisp (fmakunbound sym)))
123
    (labels ((asym (x) (multiple-value-bind (s p) (if (consp x) (values (car x) (cadr x)) (values x :asdf))
124 125
                         (find-symbol* s p nil)))
             (asyms (l) (mapcar #'asym l)))
126
      (loop* :for (name superclasses slots) :in redefined-classes
127 128 129
             :for sym = (find-symbol* name :asdf nil)
             :when (and sym (find-class sym))
             :do (eval `(defclass ,sym ,(asyms superclasses) ,(asyms slots)))))))
130

131

132
;;; Self-upgrade functions
133
(with-upgradability ()
134 135
  ;; This private function is called at the end of asdf/footer and ensures that,
  ;; *if* this loading of ASDF was an upgrade, then all registered cleanup functions will be called.
136 137 138 139 140
  (defun cleanup-upgraded-asdf (&optional (old-version (first *previous-asdf-versions*)))
    (let ((new-version (asdf-version)))
      (unless (equal old-version new-version)
        (push new-version *previous-asdf-versions*)
        (when old-version
141 142 143 144 145
          (if (version<= new-version old-version)
              (error (compatfmt "~&~@<; ~@;Downgraded ASDF from version ~A to version ~A~@:>~%")
                     old-version new-version)
              (asdf-message (compatfmt "~&~@<; ~@;Upgraded ASDF from version ~A to version ~A~@:>~%")
                            old-version new-version))
146 147 148 149 150 151
          ;; In case the previous version was too old to be forward-compatible, clear systems.
          ;; TODO: if needed, we may have to define a separate hook to run
          ;; in case of forward-compatible upgrade.
          ;; Or to move the tests forward-compatibility test inside each hook function?
          (unless (version<= *oldest-forward-compatible-asdf-version* old-version)
            (call-functions (reverse *post-upgrade-cleanup-hook*)))
152
          t))))
153

154 155
  (defun upgrade-asdf ()
    "Try to upgrade of ASDF. If a different version was used, return T.
156
   We need do that before we operate on anything that may possibly depend on ASDF."
157 158
    (let ((*load-print* nil)
          (*compile-print* nil))
159
      (handler-bind (((or style-warning) #'muffle-warning))
160 161
        (symbol-call :asdf :load-system :asdf :verbose nil))))

162 163
  ;; Register the upgrade-configuration function from UIOP,
  ;; to ensure configuration is upgraded as needed.
164
  (register-hook-function '*post-upgrade-cleanup-hook* 'upgrade-configuration))
165