(in-package #:quicklisp) (defun show-wrapped-list (words &key (indent 4) (margin 60)) (let ((*print-right-margin* margin) (*print-pretty* t) (*print-escape* nil) (prefix (make-string indent :initial-element #\Space))) (pprint-logical-block (nil words :per-line-prefix prefix) (pprint-fill *standard-output* (sort (copy-seq words) #'string<) nil)) (fresh-line) (finish-output))) (defun recursively-install (name) (labels ((recurse (name) (let ((system (find-system name))) (unless system (error "Unknown system ~S" name)) (ensure-installed system) (mapcar #'recurse (required-systems system)) name))) (with-consistent-dists (recurse name)))) (defclass load-strategy () ((name :initarg :name :accessor name) (asdf-systems :initarg :asdf-systems :accessor asdf-systems) (quicklisp-systems :initarg :quicklisp-systems :accessor quicklisp-systems))) (defmethod print-object ((strategy load-strategy) stream) (print-unreadable-object (strategy stream :type t) (format stream "~S (~D asdf, ~D quicklisp)" (name strategy) (length (asdf-systems strategy)) (length (quicklisp-systems strategy))))) (defgeneric quicklisp-releases (strategy) (:method (strategy) (remove-duplicates (mapcar 'release (quicklisp-systems strategy))))) (defgeneric quicklisp-release-table (strategy) (:method ((strategy load-strategy)) (let ((table (make-hash-table))) (dolist (system (quicklisp-systems strategy)) (push system (gethash (release system) table nil))) table))) (define-condition system-not-found (error) ((name :initarg :name :reader system-not-found-name)) (:report (lambda (condition stream) (format stream "System ~S not found" (system-not-found-name condition)))) (:documentation "This condition is signaled by QUICKLOAD when a system given to load is not available via ASDF or a Quicklisp dist.")) (defun compute-load-strategy (name) (setf name (string-downcase name)) (let ((asdf-systems '()) (quicklisp-systems '())) (labels ((recurse (name) (let ((asdf-system (asdf:find-system name nil)) (quicklisp-system (find-system name))) (cond (asdf-system (push asdf-system asdf-systems)) (quicklisp-system (push quicklisp-system quicklisp-systems) (dolist (subname (required-systems quicklisp-system)) (recurse subname))) (t (cerror "Try again" 'system-not-found :name name) (recurse name)))))) (with-consistent-dists (recurse name))) (make-instance 'load-strategy :name name :asdf-systems (remove-duplicates asdf-systems) :quicklisp-systems (remove-duplicates quicklisp-systems)))) (defun show-load-strategy (strategy) (format t "To load ~S:~%" (name strategy)) (let ((asdf-systems (asdf-systems strategy)) (releases (quicklisp-releases strategy))) (when asdf-systems (format t " Load ~D ASDF system~:P:~%" (length asdf-systems)) (show-wrapped-list (mapcar 'asdf:component-name asdf-systems))) (when releases (format t " Install ~D Quicklisp release~:P:~%" (length releases)) (show-wrapped-list (mapcar 'name releases))))) (defvar *macroexpand-progress-in-progress* nil) (defun macroexpand-progress-fun (old-hook &key (char #\.) (chars-per-line 50) (forms-per-char 250)) (let ((output-so-far 0) (seen-so-far 0)) (labels ((finish-line () (when (plusp output-so-far) (dotimes (i (- chars-per-line output-so-far)) (write-char char)) (terpri) (setf output-so-far 0))) (show-string (string) (let* ((length (length string)) (new-output (+ length output-so-far))) (cond ((< chars-per-line new-output) (finish-line) (write-string string) (setf output-so-far length)) (t (write-string string) (setf output-so-far new-output)))) (finish-output)) (show-package (name) ;; Only show package markers when compiling. Showing ;; them when loading shows a bunch of ASDF system ;; package noise. (when *compile-file-pathname* (finish-line) (show-string (format nil "[package ~(~A~)]" name))))) (lambda (fun form env) (when (and (consp form) (eq (first form) 'cl:defpackage) (ignore-errors (string (second form)))) (show-package (second form))) (incf seen-so-far) (when (<= forms-per-char seen-so-far) (setf seen-so-far 0) (write-char char) (finish-output) (incf output-so-far) (when (<= chars-per-line output-so-far) (setf output-so-far 0) (terpri) (finish-output))) (funcall old-hook fun form env))))) (defun call-with-macroexpand-progress (fun) (let ((*macroexpand-hook* (if *macroexpand-progress-in-progress* *macroexpand-hook* (macroexpand-progress-fun *macroexpand-hook*))) (*macroexpand-progress-in-progress* t)) (funcall fun) (terpri))) (defun apply-load-strategy (strategy) (map nil 'ensure-installed (quicklisp-releases strategy)) (call-with-macroexpand-progress (lambda () (format t "~&; Loading ~S~%" (name strategy)) (asdf:load-system (name strategy) :verbose nil)))) (defun autoload-system-and-dependencies (name &key prompt) "Try to load the system named by NAME, automatically loading any Quicklisp-provided systems first, and catching ASDF missing dependencies too if possible." (setf name (string-downcase name)) (with-simple-restart (abort "Give up on ~S" name) (let ((tried-so-far (make-hash-table :test 'equalp))) (tagbody retry (handler-case (let ((strategy (compute-load-strategy name))) (show-load-strategy strategy) (when (or (not prompt) (press-enter-to-continue)) (apply-load-strategy strategy))) (asdf:missing-dependency-of-version (c) ;; Nothing Quicklisp can do to recover from this, so just ;; resignal (error c)) (asdf:missing-dependency (c) (let ((parent (asdf::missing-required-by c)) (missing (asdf::missing-requires c))) (typecase parent ((or null asdf:system) ;; NIL parent comes from :defsystem-depends-on failures (if (gethash missing tried-so-far) (error "Dependency looping -- already tried to load ~ ~A" missing) (setf (gethash missing tried-so-far) missing)) (autoload-system-and-dependencies missing :prompt prompt) (go retry)) (t ;; Error isn't from a system dependency, so there's ;; nothing to autoload (error c)))))))) name)) (defvar *initial-dist-url* "http://beta.quicklisp.org/dist/quicklisp.txt") (defun dists-initialized-p () (not (not (ignore-errors (truename (qmerge "dists/")))))) (defun quickstart-parameter (name &optional default) (let* ((package (find-package '#:quicklisp-quickstart)) (symbol (and package (find-symbol (string '#:*quickstart-parameters*) package))) (plist (and symbol (symbol-value symbol))) (parameter (and plist (getf plist name)))) (or parameter default))) (defun maybe-initial-setup () "Run the steps needed when Quicklisp setup is run for the first time after the quickstart installation." (let ((quickstart-proxy-url (quickstart-parameter :proxy-url)) (quickstart-initial-dist-url (quickstart-parameter :initial-dist-url))) (when (and quickstart-proxy-url (not *proxy-url*)) (setf *proxy-url* quickstart-proxy-url) (setf (config-value "proxy-url") quickstart-proxy-url)) (unless (dists-initialized-p) (let ((target (qmerge "dists/quicklisp/distinfo.txt")) (url (or quickstart-initial-dist-url *initial-dist-url*))) (ensure-directories-exist target) (install-dist url :prompt nil))))) (defun setup () (unless (member 'system-definition-searcher asdf:*system-definition-search-functions*) (setf asdf:*system-definition-search-functions* (append asdf:*system-definition-search-functions* (list 'local-projects-searcher 'system-definition-searcher)))) (let ((files (nconc (directory (qmerge "local-init/*.lisp")) (directory (qmerge "local-init/*.cl"))))) (with-simple-restart (abort "Stop loading local setup files") (dolist (file (sort files #'string< :key #'pathname-name)) (with-simple-restart (skip "Skip local setup file ~S" file) ;; Don't try to load Emacs lock files, other hidden files (unless (char= (char (pathname-name file) 0) #\.) (load file)))))) (maybe-initial-setup) (ensure-directories-exist (qmerge "local-projects/")) (pushnew :quicklisp *features*) t)