;;; -*- mode: Lisp; Syntax: Common-Lisp; -*- ;;; ;;; Copyright (c) 2009 by the authors. ;;; ;;; See LICENCE for details. (in-package :hu.dwim.asdf) ;;;;;; ;;; Package support (defclass system-with-package (asdf:system) ((package-name :initarg :package-name :accessor system-package-name))) (defmethod reinitialize-instance :after ((system system-with-package) &rest args &key &allow-other-keys) (declare (ignore args)) (unless (slot-boundp system 'package-name) (setf (system-package-name system) (string-upcase (asdf:component-name system))))) (defclass system-with-target () ((target-system-name :initarg :target-system-name :accessor system-target-system-name))) (defmethod reinitialize-instance :after ((system system-with-target) &rest args &key &allow-other-keys) (declare (ignore args)) (flet ((maybe-copy-slot (slot-name source target) (when (and (slot-boundp source slot-name) (not (slot-boundp target slot-name))) (setf (slot-value target slot-name) (slot-value source slot-name))))) (let* ((system-name (string-downcase (asdf:component-name system))) (last-dot-position (position #\. system-name :from-end t))) (unless (slot-boundp system 'target-system-name) (setf (system-target-system-name system) (subseq system-name 0 last-dot-position))) (let ((target-system (find-system (system-target-system-name system) nil))) (when target-system (maybe-copy-slot 'asdf::author target-system system) (maybe-copy-slot 'asdf::licence target-system system) (unless (slot-boundp system 'asdf::description) (setf (slot-value system 'asdf::description) (concatenate 'string (string-capitalize (subseq system-name (1+ last-dot-position))) " for " (system-target-system-name system))))))))) ;;;;;; ;;; DWIM system (defvar *muffle-optimization-warnings* t) (defclass hu.dwim.cl-source-file (asdf:cl-source-file) ()) (defclass system-with-output () ((compile-output :initform nil :initarg :compile-output :accessor system-compile-output) (load-output :initform nil :initarg :load-output :accessor system-load-output))) (defclass hu.dwim.base-system (system-with-output system-with-package) ()) (defmethod shared-initialize :around ((system hu.dwim.base-system) slot-names &rest initargs) (unless (getf initargs :license) (setf (getf initargs :license) "BSD or Bugroff")) (unless (getf initargs :author) (setf (getf initargs :author) '("Tamás Borbély " "Attila Lendvai " "Levente Mészáros "))) (apply #'call-next-method system slot-names initargs)) (defclass hu.dwim.system (hu.dwim.base-system) ((test-system-name :initarg :test-system-name :accessor system-test-system-name) (documentation-system-name :initarg :documentation-system-name :accessor system-documentation-system-name))) (defclass hu.dwim.test-system (system-with-target hu.dwim.base-system) ((test-name :initform "TEST" :initarg :test-name :accessor system-test-name) (test-result :initform nil :initarg :test-result :accessor system-test-result) (test-output :initform nil :initarg :test-output :accessor system-test-output))) (defmethod shared-initialize :around ((system hu.dwim.test-system) slot-names &rest initargs) (unless (getf initargs :description) (setf (getf initargs :description) "Test system for the similarly named system.")) (apply #'call-next-method system slot-names initargs)) (defclass hu.dwim.documentation-system (system-with-target hu.dwim.base-system) ()) (defmethod shared-initialize :around ((system hu.dwim.documentation-system) slot-names &rest initargs) (unless (getf initargs :description) (setf (getf initargs :description) "Documentation for the similarly named system. It should contain formally processable data and its contents should be available at http://dwim.hu")) (apply #'call-next-method system slot-names initargs)) (defmacro with-capturing-output (place &body forms) (let ((stream (gensym "STREAM"))) `(let* ((,stream (make-string-output-stream)) (*standard-output* (make-broadcast-stream *standard-output* ,stream)) (*error-output* (make-broadcast-stream *error-output* ,stream))) ,@forms (setf ,place (concatenate 'string ,place (get-output-stream-string ,stream)))))) (defmethod reinitialize-instance :after ((system hu.dwim.system) &rest args &key &allow-other-keys) (declare (ignore args)) (unless (slot-boundp system 'test-system-name) (setf (system-test-system-name system) (concatenate 'string (string-downcase (asdf:component-name system)) "/test"))) (unless (slot-boundp system 'documentation-system-name) (setf (system-documentation-system-name system) (concatenate 'string (string-downcase (asdf:component-name system)) "/documentation")))) (defmethod asdf::module-default-component-class ((class hu.dwim.test-system)) 'hu.dwim.cl-source-file) (defmethod asdf::module-default-component-class ((class hu.dwim.system)) 'hu.dwim.cl-source-file) (defmethod perform :around ((op asdf:operation) (component hu.dwim.cl-source-file)) (let ((*features* *features*) (*readtable* (copy-readtable *readtable*)) (*package* *package*) (hu.dwim.common-package (find-package :hu.dwim.common))) (when hu.dwim.common-package ;; when the hu.dwim.common package is available, then we read lisp files into that, so that hu.dwim.common:in-package can shadow cl:in-package. ;; see hu.dwim.def/source/extended-package.lisp for more info. (setf *package* hu.dwim.common-package)) (debug-only (pushnew :debug *features*)) (call-in-system-environment op (asdf:component-system component) #'call-next-method))) (defmethod perform :around ((op compile-op) (component hu.dwim.cl-source-file)) (with-capturing-output (system-compile-output (asdf:component-system component)) (call-next-method))) (defmethod perform :around ((op load-op) (component hu.dwim.cl-source-file)) (with-capturing-output (system-load-output (asdf:component-system component)) (call-next-method))) (defgeneric call-in-system-environment (operation system function) (:method ((op asdf:operation) (system asdf:system) function) (if *muffle-optimization-warnings* (call-with-muffled-boring-compiler-warnings function) (funcall function)))) (defmethod perform ((op test-op) (system hu.dwim.system)) (let ((test-system (find-system (system-test-system-name system) nil))) (if (typep test-system 'hu.dwim.test-system) (progn (load-system test-system) (run-test-suite test-system)) (warn "There is no test system for ~A, no tests were run." system)))) (defgeneric run-test-suite (system) (:method ((system asdf:system)) (warn "Don't know how to run tests suite for ~A" system)) (:method :around ((system hu.dwim.test-system)) (with-capturing-output (system-test-output system) (setf (system-test-result system) (call-next-method)))) (:method ((system hu.dwim.test-system)) (if (find-package :hu.dwim.stefil) (let ((package-name (system-package-name system))) (if package-name (let ((test-name (find-symbol (system-test-name system) package-name))) (funcall (find-symbol "FUNCALL-TEST-WITH-FEEDBACK-MESSAGE" :hu.dwim.stefil) test-name)) (warn "There is no test package for ~A, no tests were run." system))) (call-next-method)))) ;;;;;; ;;; Develop ;; this is only needed to workaround asdf's rigidity (ASDF protects *package* by rebinding it, but we want to set it) (defvar *development-package*) (defclass develop-op (non-propagating-operation) ()) (defmethod asdf:operation-done-p ((operation develop-op) (component asdf:component)) nil) (defmethod perform ((operation develop-op) (component asdf:component)) nil) (defmethod perform :before ((operation develop-op) (system asdf:system)) (with-simple-restart (continue "Give up loading Swank and continue...") (load-system :swank) (set (read-from-string "swank:*globally-redirect-io*") t))) (defmethod perform :after ((operation develop-op) (system asdf:system)) (load-system :hu.dwim.debug) (use-package :hu.dwim.debug :hu.dwim.common) (do-external-symbols (symbol :hu.dwim.debug) (export symbol :hu.dwim.common)) (find-and-load-swank-integration-systems) (declaim (optimize (debug 3))) (pushnew :debug *features*) (warn "Pushed :debug in *features* and issued (declaim (optimize (debug 3))) to help later C-c C-c'ing")) (defmethod perform ((operation develop-op) (system asdf:system)) (let ((system-to-load (or (find-system (if (typep system 'hu.dwim.system) (system-test-system-name system) (concatenate 'string (asdf:component-name system) "/test")) nil) system)) (quickload-fn (when (find-package '#:quicklisp) (find-symbol (string '#:quickload) (find-package '#:quicklisp))))) (if quickload-fn (funcall quickload-fn (asdf:component-name system-to-load) :verbose t) (load-system system-to-load)) (when (typep system-to-load 'system-with-package) (let ((package (find-package (system-package-name system-to-load)))) (when package (setf *development-package* package)))))) (defun develop-system (system &rest args &key force (verbose t) version) "Shorthand for `(operate 'asdf:develop-op system)`. See [operate][] for details." (declare (ignore force version)) (let ((*development-package* nil)) (multiple-value-prog1 (apply 'asdf:operate 'develop-op system :verbose verbose args) (when *development-package* (setf *package* *development-package*) (warn "Changed *package* to ~A" *package*))))) ;;;;;; ;;; Util (defun system-pathname (name) (asdf:component-pathname (find-system name))) (defun system-directory (name) (make-pathname :directory (pathname-directory (system-pathname name)))) (defun system-loaded-p (system-name) (let ((system (find-system system-name))) (when system (asdf:component-loaded-p system)))) (defun map-asdf-source-registry-directories (visitor) (loop :for asd-file :being :the :hash-value :of asdf::*source-registry* :do (funcall visitor (make-pathname :directory (pathname-directory asd-file))))) (defun find-all-swank-integration-systems () (map-asdf-source-registry-directories (lambda (directory) (dolist (file (directory (merge-pathnames directory (make-pathname :name :wild :type "asd")))) (let ((name (pathname-name file))) (when (and (search "hu.dwim" name) (search "+swank" name)) (find-system name))))))) (defun load-swank-integration-systems () "Loads the +swank systems for the already loaded systems." (map nil (lambda (name) (let ((system (asdf:find-system name))) (when (and (search "+swank" name) (not (system-loaded-p name)) (every 'system-loaded-p (collect-system-dependencies system))) (with-simple-restart (skip-system "Skip loading swank integration ~A" system) (load-system system))))) (asdf:registered-systems))) (defun find-and-load-swank-integration-systems () (find-all-swank-integration-systems) (load-swank-integration-systems)) (defun %iterate-system-dependencies-1 (function system) (check-type system asdf:system) ;; NOTE: it's not clear how to iterate dependencies, see this old discussion: ;; http://article.gmane.org/gmane.lisp.asdf.devel/3105 ;; although ASDF:COMPONENT-SIDEWAY-DEPENDENCIES might be newer than that discussion. (dolist (dependency (asdf:component-sideway-dependencies system)) ;; NOTE: there may be dependencies here like this: (:VERSION :METATILITIES-BASE "0.6.6") (when (consp dependency) (case (first dependency) (:version (setf dependency (second dependency))) (t (error "Don't know how to interpret the following ASDF dependency specification: ~S" dependency)))) (funcall function (asdf:find-system dependency)))) (defun iterate-system-dependencies (function system &key (transitive nil)) (setf system (find-system system)) (if transitive (let ((dependencies '())) (labels ((recurse (system) (%iterate-system-dependencies-1 (lambda (dependency) (unless (member dependency dependencies) (push dependency dependencies) (recurse dependency))) system))) (recurse system) (map nil function dependencies))) (%iterate-system-dependencies-1 function system)) (values)) (defun map-system-dependencies (function system &key (transitive nil)) (let ((result '())) (iterate-system-dependencies (lambda (dependency) (push (funcall function dependency) result)) system :transitive transitive) result)) (defun collect-system-dependencies (system &key (transitive nil)) (map-system-dependencies 'identity system :transitive transitive)) (defmacro do-system-dependencies ((variable-name system-name &key (transitive nil)) &body body) (let ((body-fn (gensym "DSD-BODY"))) `(block nil (flet ((,body-fn (,variable-name) ,@body)) (iterate-system-dependencies #',body-fn ,system-name :transitive ,transitive))))) (reinitialize-instance (change-class (find-system :hu.dwim.asdf) 'hu.dwim.system))