;;; swank-asdf.lisp -- ASDF support ;; ;; Authors: Daniel Barlow ;; Marco Baringer ;; Edi Weitz ;; Francois-Rene Rideau ;; and others ;; License: Public Domain ;; (in-package :swank) (eval-when (:compile-toplevel :load-toplevel :execute) ;;; The best way to load ASDF is from an init file of an ;;; implementation. If ASDF is not loaded at the time swank-asdf is ;;; loaded, it will be tried first with (require "asdf"), if that ;;; doesn't help and *asdf-path* is set, it will be loaded from that ;;; file. ;;; To set *asdf-path* put the following into ~/.swank.lisp: ;;; (defparameter swank::*asdf-path* #p"/path/to/asdf/asdf.lisp") (defvar *asdf-path* nil "Path to asdf.lisp file, to be loaded in case (require \"asdf\") fails.")) (eval-when (:compile-toplevel :load-toplevel :execute) (unless (member :asdf *features*) (ignore-errors (funcall 'require "asdf")))) (eval-when (:compile-toplevel :load-toplevel :execute) (unless (member :asdf *features*) (handler-bind ((warning #'muffle-warning)) (when *asdf-path* (load *asdf-path* :if-does-not-exist nil))))) ;; If still not found, error out. (eval-when (:compile-toplevel :load-toplevel :execute) (unless (member :asdf *features*) (error "Could not load ASDF. Please update your implementation or install a recent release of ASDF and in your ~~/.swank.lisp specify: (defparameter swank::*asdf-path* #p\"/path/containing/asdf/asdf.lisp\")"))) ;;; If ASDF is too old, punt. ;; As of January 2014, Quicklisp has been providing 2.26 for a year ;; (and previously had 2.014.6 for over a year), whereas ;; all SLIME-supported implementations provide ASDF3 (i.e. 2.27 or later) ;; except LispWorks (stuck with 2.019) and SCL (which hasn't been released ;; in years and doesn't provide ASDF at all, but is fully supported by ASDF). ;; If your implementation doesn't provide ASDF, or provides an old one, ;; install an upgrade yourself and configure *asdf-path*. ;; It's just not worth the hassle supporting something ;; that doesn't even have COERCE-PATHNAME. ;; ;; NB: this version check is duplicated in swank-loader.lisp so that we don't ;; try to load this contrib when ASDF is too old since that will abort the SLIME ;; connection. #-asdf3 (eval-when (:compile-toplevel :load-toplevel :execute) (unless (and #+asdf2 (asdf:version-satisfies (asdf:asdf-version) "2.14.6")) (error "Your ASDF is too old. ~ The oldest version supported by swank-asdf is 2.014.6."))) ;;; Import functionality from ASDF that isn't available in all ASDF versions. ;;; Please do NOT depend on any of the below as reference: ;;; they are sometimes stripped down versions, for compatibility only. ;;; Indeed, they are supposed to work on *OLDER*, not *NEWER* versions of ASDF. ;;; ;;; The way I got these is usually by looking at the current definition, ;;; using git blame in one screen to locate which commit last modified it, ;;; and git log in another to determine which release that made it in. ;;; It is OK for some of the below definitions to be or become obsolete, ;;; as long as it will make do with versions older than the tagged version: ;;; if ASDF is more recent, its more recent version will win. ;;; ;;; If your software is hacking ASDF, use its internals. ;;; If you want ASDF utilities in user software, please use ASDF-UTILS. (defun asdf-at-least (version) (asdf:version-satisfies (asdf:asdf-version) version)) (defmacro asdefs (version &rest defs) (flet ((defun* (version name aname rest) `(progn (defun ,name ,@rest) (declaim (notinline ,name)) (when (asdf-at-least ,version) (setf (fdefinition ',name) (fdefinition ',aname))))) (defmethod* (version aname rest) `(unless (asdf-at-least ,version) (defmethod ,aname ,@rest))) (defvar* (name aname rest) `(progn (define-symbol-macro ,name ,aname) (defvar ,aname ,@rest)))) `(progn ,@(loop :for (def name . args) :in defs :for aname = (intern (string name) :asdf) :collect (ecase def ((defun) (defun* version name aname args)) ((defmethod) (defmethod* version aname args)) ((defvar) (defvar* name aname args))))))) (asdefs "2.15" (defvar *wild* #-cormanlisp :wild #+cormanlisp "*") (defun collect-asds-in-directory (directory collect) (map () collect (directory-asd-files directory))) (defun register-asd-directory (directory &key recurse exclude collect) (if (not recurse) (collect-asds-in-directory directory collect) (collect-sub*directories-asd-files directory :exclude exclude :collect collect)))) (asdefs "2.16" (defun load-sysdef (name pathname) (declare (ignore name)) (let ((package (asdf::make-temporary-package))) (unwind-protect (let ((*package* package) (*default-pathname-defaults* (asdf::pathname-directory-pathname (translate-logical-pathname pathname)))) (asdf::asdf-message "~&; Loading system definition from ~A into ~A~%" ; pathname package) (load pathname)) (delete-package package)))) (defun directory* (pathname-spec &rest keys &key &allow-other-keys) (apply 'directory pathname-spec (append keys '#.(or #+allegro '(:directories-are-files nil :follow-symbolic-links nil) #+clozure '(:follow-links nil) #+clisp '(:circle t :if-does-not-exist :ignore) #+(or cmu scl) '(:follow-links nil :truenamep nil) #+sbcl (when (find-symbol "RESOLVE-SYMLINKS" '#:sb-impl) '(:resolve-symlinks nil))))))) (asdefs "2.17" (defun collect-sub*directories-asd-files (directory &key (exclude asdf::*default-source-registry-exclusions*) collect) (asdf::collect-sub*directories directory (constantly t) (lambda (x) (not (member (car (last (pathname-directory x))) exclude :test #'equal))) (lambda (dir) (collect-asds-in-directory dir collect)))) (defun system-source-directory (system-designator) (asdf::pathname-directory-pathname (asdf::system-source-file system-designator))) (defun filter-logical-directory-results (directory entries merger) (if (typep directory 'logical-pathname) (loop for f in entries when (if (typep f 'logical-pathname) f (let ((u (ignore-errors (funcall merger f)))) (and u (equal (ignore-errors (truename u)) (truename f)) u))) collect it) entries)) (defun directory-asd-files (directory) (directory-files directory asdf::*wild-asd*))) (asdefs "2.19" (defun subdirectories (directory) (let* ((directory (asdf::ensure-directory-pathname directory)) #-(or abcl cormanlisp xcl) (wild (asdf::merge-pathnames* #-(or abcl allegro cmu lispworks sbcl scl xcl) asdf::*wild-directory* #+(or abcl allegro cmu lispworks sbcl scl xcl) "*.*" directory)) (dirs #-(or abcl cormanlisp xcl) (ignore-errors (directory* wild . #.(or #+clozure '(:directories t :files nil) #+mcl '(:directories t)))) #+(or abcl xcl) (system:list-directory directory) #+cormanlisp (cl::directory-subdirs directory)) #+(or abcl allegro cmu lispworks sbcl scl xcl) (dirs (loop for x in dirs for d = #+(or abcl xcl) (extensions:probe-directory x) #+allegro (excl:probe-directory x) #+(or cmu sbcl scl) (asdf::directory-pathname-p x) #+lispworks (lw:file-directory-p x) when d collect #+(or abcl allegro xcl) d #+(or cmu lispworks sbcl scl) x))) (filter-logical-directory-results directory dirs (let ((prefix (or (normalize-pathname-directory-component (pathname-directory directory)) ;; because allegro 8.x returns NIL for #p"FOO:" '(:absolute)))) (lambda (d) (let ((dir (normalize-pathname-directory-component (pathname-directory d)))) (and (consp dir) (consp (cdr dir)) (make-pathname :defaults directory :name nil :type nil :version nil :directory (append prefix (make-pathname-component-logical (last dir)))))))))))) (asdefs "2.21" (defun component-loaded-p (c) (and (gethash 'load-op (asdf::component-operation-times (asdf::find-component c nil))) t)) (defun normalize-pathname-directory-component (directory) (cond #-(or cmu sbcl scl) ((stringp directory) `(:absolute ,directory) directory) ((or (null directory) (and (consp directory) (member (first directory) '(:absolute :relative)))) directory) (t (error "Unrecognized pathname directory component ~S" directory)))) (defun make-pathname-component-logical (x) (typecase x ((eql :unspecific) nil) #+clisp (string (string-upcase x)) #+clisp (cons (mapcar 'make-pathname-component-logical x)) (t x))) (defun make-pathname-logical (pathname host) (make-pathname :host host :directory (make-pathname-component-logical (pathname-directory pathname)) :name (make-pathname-component-logical (pathname-name pathname)) :type (make-pathname-component-logical (pathname-type pathname)) :version (make-pathname-component-logical (pathname-version pathname))))) (asdefs "2.22" (defun directory-files (directory &optional (pattern asdf::*wild-file*)) (let ((dir (pathname directory))) (when (typep dir 'logical-pathname) (when (wild-pathname-p dir) (error "Invalid wild pattern in logical directory ~S" directory)) (unless (member (pathname-directory pattern) '(() (:relative)) :test 'equal) (error "Invalid file pattern ~S for logical directory ~S" pattern directory)) (setf pattern (make-pathname-logical pattern (pathname-host dir)))) (let ((entries (ignore-errors (directory* (asdf::merge-pathnames* pattern dir))))) (filter-logical-directory-results directory entries (lambda (f) (make-pathname :defaults dir :name (make-pathname-component-logical (pathname-name f)) :type (make-pathname-component-logical (pathname-type f)) :version (make-pathname-component-logical (pathname-version f))))))))) (asdefs "2.26.149" (defmethod component-relative-pathname ((system asdf:system)) (asdf::coerce-pathname (and (slot-boundp system 'asdf::relative-pathname) (slot-value system 'asdf::relative-pathname)) :type :directory :defaults (system-source-directory system))) (defun load-asd (pathname &key name &allow-other-keys) (asdf::load-sysdef (or name (string-downcase (pathname-name pathname))) pathname))) ;;; Taken from ASDF 1.628 (defmacro while-collecting ((&rest collectors) &body body) `(asdf::while-collecting ,collectors ,@body)) ;;; Now for SLIME-specific stuff (defun asdf-operation (operation) (or (asdf::find-symbol* operation :asdf) (error "Couldn't find ASDF operation ~S" operation))) (defun map-system-components (fn system) (map-component-subcomponents fn (asdf:find-system system))) (defun map-component-subcomponents (fn component) (when component (funcall fn component) (when (typep component 'asdf:module) (dolist (c (asdf:module-components component)) (map-component-subcomponents fn c))))) ;;; Maintaining a pathname to component table (defvar *pathname-component* (make-hash-table :test 'equal)) (defun clear-pathname-component-table () (clrhash *pathname-component*)) (defun register-system-pathnames (system) (map-system-components 'register-component-pathname system)) (defun recompute-pathname-component-table () (clear-pathname-component-table) (asdf::map-systems 'register-system-pathnames)) (defun pathname-component (x) (gethash (pathname x) *pathname-component*)) (defmethod asdf:component-pathname :around ((component asdf:component)) (let ((p (call-next-method))) (when (pathnamep p) (setf (gethash p *pathname-component*) component)) p)) (defun register-component-pathname (component) (asdf:component-pathname component)) (recompute-pathname-component-table) ;;; This is a crude hack, see ASDF's LP #481187. (defslimefun who-depends-on (system) (flet ((system-dependencies (op system) (mapcar (lambda (dep) (asdf::coerce-name (if (consp dep) (second dep) dep))) (cdr (assoc op (asdf:component-depends-on op system)))))) (let ((system-name (asdf::coerce-name system)) (result)) (asdf::map-systems (lambda (system) (when (member system-name (system-dependencies 'asdf:load-op system) :test #'string=) (push (asdf:component-name system) result)))) result))) (defmethod xref-doit ((type (eql :depends-on)) thing) (when (typep thing '(or string symbol)) (loop for dependency in (who-depends-on thing) for asd-file = (asdf:system-definition-pathname dependency) when asd-file collect (list dependency (swank/backend:make-location `(:file ,(namestring asd-file)) `(:position 1) `(:snippet ,(format nil "(defsystem :~A" dependency) :align t)))))) (defslimefun operate-on-system-for-emacs (system-name operation &rest keywords) "Compile and load SYSTEM using ASDF. Record compiler notes signalled as `compiler-condition's." (collect-notes (lambda () (apply #'operate-on-system system-name operation keywords)))) (defun operate-on-system (system-name operation-name &rest keyword-args) "Perform OPERATION-NAME on SYSTEM-NAME using ASDF. The KEYWORD-ARGS are passed on to the operation. Example: \(operate-on-system \"cl-ppcre\" 'compile-op :force t)" (handler-case (with-compilation-hooks () (apply #'asdf:operate (asdf-operation operation-name) system-name keyword-args) t) ((or asdf:compile-error #+asdf3 asdf/lisp-build:compile-file-error) () nil))) (defun unique-string-list (&rest lists) (sort (delete-duplicates (apply #'append lists) :test #'string=) #'string<)) (defslimefun list-all-systems-in-central-registry () "Returns a list of all systems in ASDF's central registry AND in its source-registry. (legacy name)" (unique-string-list (mapcar #'pathname-name (while-collecting (c) (loop for dir in asdf:*central-registry* for defaults = (eval dir) when defaults do (collect-asds-in-directory defaults #'c)) (asdf:ensure-source-registry) (if (or #+asdf3 t #-asdf3 (asdf:version-satisfies (asdf:asdf-version) "2.15")) (loop :for k :being :the :hash-keys :of asdf::*source-registry* :do (c k)) #-asdf3 (dolist (entry (asdf::flatten-source-registry)) (destructuring-bind (directory &key recurse exclude) entry (register-asd-directory directory :recurse recurse :exclude exclude :collect #'c)))))))) (defslimefun list-all-systems-known-to-asdf () "Returns a list of all systems ASDF knows already." (while-collecting (c) (asdf::map-systems (lambda (system) (c (asdf:component-name system)))))) (defslimefun list-asdf-systems () "Returns the systems in ASDF's central registry and those which ASDF already knows." (unique-string-list (list-all-systems-known-to-asdf) (list-all-systems-in-central-registry))) (defun asdf-component-source-files (component) (while-collecting (c) (labels ((f (x) (typecase x (asdf:source-file (c (asdf:component-pathname x))) (asdf:module (map () #'f (asdf:module-components x)))))) (f component)))) (defun make-operation (x) #+#.(swank/backend:with-symbol 'make-operation 'asdf) (asdf:make-operation x) #-#.(swank/backend:with-symbol 'make-operation 'asdf) (make-instance x)) (defun asdf-component-output-files (component) (while-collecting (c) (labels ((f (x) (typecase x (asdf:source-file (map () #'c (asdf:output-files (make-operation 'asdf:compile-op) x))) (asdf:module (map () #'f (asdf:module-components x)))))) (f component)))) (defslimefun asdf-system-files (name) (let* ((system (asdf:find-system name)) (files (mapcar #'namestring (cons (asdf:system-definition-pathname system) (asdf-component-source-files system)))) (main-file (find name files :test #'equalp :key #'pathname-name :start 1))) (if main-file (cons main-file (remove main-file files :test #'equal :count 1)) files))) (defslimefun asdf-system-loaded-p (name) (component-loaded-p name)) (defslimefun asdf-system-directory (name) (namestring (translate-logical-pathname (asdf:system-source-directory name)))) (defun pathname-system (pathname) (let ((component (pathname-component pathname))) (when component (asdf:component-name (asdf:component-system component))))) (defslimefun asdf-determine-system (file buffer-package-name) (or (and file (pathname-system file)) (and file (progn ;; If not found, let's rebuild the table first (recompute-pathname-component-table) (pathname-system file))) ;; If we couldn't find an already defined system, ;; try finding a system that's named like BUFFER-PACKAGE-NAME. (loop with package = (guess-buffer-package buffer-package-name) for name in (package-names package) for system = (asdf:find-system (asdf::coerce-name name) nil) when (and system (or (not file) (pathname-system file))) return (asdf:component-name system)))) (defslimefun delete-system-fasls (name) (let ((removed-count (loop for file in (asdf-component-output-files (asdf:find-system name)) when (probe-file file) count it and do (delete-file file)))) (format nil "~d file~:p ~:*~[were~;was~:;were~] removed" removed-count))) (defvar *recompile-system* nil) (defmethod asdf:operation-done-p :around ((operation asdf:compile-op) component) (unless (eql *recompile-system* (asdf:component-system component)) (call-next-method))) (defslimefun reload-system (name) (let ((*recompile-system* (asdf:find-system name))) (operate-on-system-for-emacs name 'asdf:load-op))) ;; Doing list-all-systems-in-central-registry might be quite slow ;; since it accesses a file-system, so run it once at the background ;; to initialize caches. (when (eql *communication-style* :spawn) (spawn (lambda () (ignore-errors (list-all-systems-in-central-registry))) :name "init-asdf-fs-caches")) ;;; Hook for compile-file-for-emacs (defun try-compile-file-with-asdf (pathname load-p &rest options) (declare (ignore options)) (let ((component (pathname-component pathname))) (when component ;;(format t "~&Compiling ASDF component ~S~%" component) (let ((op (make-operation 'asdf:compile-op))) (with-compilation-hooks () (asdf:perform op component)) (when load-p (asdf:perform (make-operation 'asdf:load-op) component)) (values t t nil (first (asdf:output-files op component))))))) (defun try-compile-asd-file (pathname load-p &rest options) (declare (ignore load-p options)) (when (equalp (pathname-type pathname) "asd") (load-asd pathname) (values t t nil pathname))) (pushnew 'try-compile-asd-file *compile-file-for-emacs-hook*) ;;; (pushnew 'try-compile-file-with-asdf *compile-file-for-emacs-hook*) (provide :swank-asdf)