This is a concatenation of the slides from my lightning talk at ILC 2009
This isn't going to be rocket science
Just the observation that lisp carries a lot of ordinary magic which can add significant value to the user
And here's an example: Lisp fasls make convenient patches for end users
In my application each release comes with an empty "patches" directory
Sorry, no time now to tell you anything about this application...
... except that it's released as a "level-0 delivered LispWorks application" — meaning it's all of Common Lisp apart from compile-file
... and, though not relevant to this talk, it's a text processing engine
If the user drops fasls into that directory the application will load them
... on startup - but that could be different
Really simple to use
Nimble: 10k fasl with minimal QA vs 30M full installer for new release
If a fix comes from LispWorks support it's still a fasl and the same distribution procedure applies
Caveat: the simplicity of this system depends on trust. I happen to trust this bunch of users not to shoot themselves.
Here's (most of) the original patch loader.
(defun load-patches () (when-let (patch-files (directory (installation-pathname "patches/*.fsl"))) ;; ;; stuff cut out here - see next slide ;; (dolist (patch patch-files) (load patch))))
Almost good enough — the above would have been something of a blunt stick —
it loads everything
in order over which one has no control
Here's what I did about that (deployed and worked fine for first 30 patches)
(defun load-patches () (catch 'load-patches (when-let (patch-files (directory (installation-pathname "patches/*.fsl"))) (when-let (revoke (find-if (lambda (patch) (pathname-match-p patch "revoke")) patch-files)) (setf patch-files (cons revoke (remove revoke patch-files)))) (dolist (patch patch-files) (load patch)))))
Solution: choose a special name and if a file with that name is present load it first.
Note the catch tag.
I didn't know when I wrote this whether I'd ever need anything more sophisticated and if so what. When the time came, the above gave me sufficient control over loading order that more complex behaviour could be bootstrapped as a "revoke" patch — rewrite load-patches and call the new one instead.
Here's the current version:
(defun load-patches () (let ((*redefinition-action* nil)) (catch 'load-patches (macrolet ((fasl-file (name) (format () "~a.~a" name sys:*binary-file-type*))) (when-let (patch-files (directory (installation-pathname (fasl-file "patches/*")))) (setf patch-files (sort patch-files (lambda (first second) (or (pathname-match-p first (fasl-file "revoke")) (and (pathname-match-p first (fasl-file "revoke-*")) (not (pathname-match-p second (fasl-file "revoke-*")))))))) (let ((all-revoked nil) (revoked-count 0) (loaded-count 0)) (dolist (patch patch-files) (let ((enough-name (make-pathname :type nil :defaults (enough-installation-namestring patch)))) (if (loop for revoked in all-revoked thereis (pathname-match-p patch revoked)) (progn (log-message :debug "Revoking patch ~a" enough-name) (incf revoked-count)) (let ((more-revoked (catch 'revoke-patches (log-message :debug "Loading patch ~a" enough-name) (load patch)))) (when (consp more-revoked) (setf all-revoked (append more-revoked all-revoked))) (incf loaded-count))))) (log-message :notice "Loaded ~d patch~@[~a~]~@[, revoked ~d~]." loaded-count (when (> loaded-count 1) "es") (when (plusp revoked-count) revoked-count)))))))
And here's how you revoke a patch:
(in-package "PROFILER-PLUS") ;; REVOKE-BUILD-GLOBAL-ENVIRONMENT.LISP ;; Nick Levine, Ravenbrook Limited, 2008-10-21 ;; ;; ;; The purpose of this document is to patch Profiler 6.0.0, revoking ;; patch build-global-environment.lisp ;; ;; This patch accompanies patch file build-global-environment-redux.lisp. (throw 'revoke-patches '("build-global-environment"))
Why revoke?
defect in original patch
when the user gets it they change their mind about what they wanted
two independent changes required to same definition
Copyright (c) 2009 Nick Levine.
This document is provided "as is", without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this document. You may make and distribute verbatim copies of this document provided that you do not charge a fee for this document or for its distribution.