;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: LW-ADD-ONS; Base: 10 -*- ;;; $Header: /usr/local/cvsrep/lw-add-ons/systems.lisp,v 1.21 2015/05/29 18:23:24 edi Exp $ ;;; Copyright (c) 2005-2015, Dr. Edmund Weitz. All rights reserved. ;;; Redistribution and use in source and binary forms, with or without ;;; modification, are permitted provided that the following conditions ;;; are met: ;;; * Redistributions of source code must retain the above copyright ;;; notice, this list of conditions and the following disclaimer. ;;; * Redistributions in binary form must reproduce the above ;;; copyright notice, this list of conditions and the following ;;; disclaimer in the documentation and/or other materials ;;; provided with the distribution. ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (in-package :lw-add-ons) (defun list-all-systems-known-to-asdf () "Returns a list of all systems ASDF knows already." (loop for name being the hash-keys of asdf::*defined-systems* collect name)) (defun list-all-systems-in-central-registry () "Returns a list of all systems in ASDF's central registry." (mapcar #'pathname-name (delete-duplicates (loop for dir in asdf:*central-registry* for defaults = (eval dir) when defaults nconc (mapcar #'file-namestring (directory (make-pathname :defaults defaults :version :newest :type "asd" :name :wild :case :local)))) :test #'string=))) (defun list-asdf-systems () "Returns the systems in ASDF's central registry and those which ASDF already knows." (nunion (list-all-systems-known-to-asdf) (list-all-systems-in-central-registry) :test #'string=)) (defun pathname-name* (name) "First \(using ASDF) converts NAME to a string if it isn't one already, then treats the resulting string as a pathname designator and returns its name component." (pathname-name (asdf::coerce-name name))) (defun component-foreign-dependencies (operation component) "The set difference of ASDF::COMPONENT-DEPENDS-ON and ASDF::COMPONENT-SELF-DEPENDENCIES." (remove-if (lambda (dep) (member (asdf:component-name component) (cdr dep) :test #'string=)) (asdf:component-depends-on operation component))) (defun translate-dep (dep) "Translate an ASDF dependency into a Common Defsystem requirement." (ecase (first dep) (asdf:compile-op `(:compile ,@(mapcar #'pathname-name* (rest dep)))) (asdf:load-op `(:load ,@(mapcar #'pathname-name* (rest dep)))))) (defun translate-deps (deps) "Translate a list of ASDF dependencies into a list of Common Defsystem requirement." (loop for dep in deps collect (translate-dep dep))) (defun make-unique-module-name (name parent-names) "Tries to create a `unique' module name from a list of parent name strings and the name of an internal module itself." ;; note that we use "->" which we hope won't occur as the name of ;; some `real' ASDF system (intern (format nil "~{~A->~}~A" (mapcar #'string-upcase parent-names) (string-upcase name)) :cl-user)) (let ((load-op (load-time-value (asdf:make-operation 'asdf:load-op))) (compile-op (load-time-value (asdf:make-operation 'asdf:compile-op)))) (defun translate-module (module &optional parent-names) "Translates the ASDF module MODULE into a Common Defsystem system definition. If the module is not a `stand-alone' system with its own .asd file then PARENT-NAMES is the list of the names of its parent systems. Returns the name of the module." ;; set to 0 temporarily as we'll have a lot of calls to INTERN (let ((*symbol-alloc-gen-num* 0) (module-name (asdf:component-name module)) (module-pathname (asdf:component-pathname module)) members rules substitutions global-deps) (labels ((to-symbol (name &key symbol no-subs) "Converts the string NAME into a symbol in the CL-USER package after upcasing it. Registers this conversion in the SUBSTITUTIONS alist unless NO-SUBS if true. If SYMBOL is not NIL take this argument as the resulting symbol, i.e. no conversion, just registration." (let ((symbol (or symbol (intern (string-upcase name) :cl-user)))) (unless no-subs (push (cons name symbol) substitutions)) symbol)) (resolve-global-deps (translated-deps) "Accepts a list of dependencies \(requirements) in Common Defsystem format and registers the involved components as members of type :SYSTEM. Returns its original argument." (dolist (translated-dep translated-deps) (dolist (candidate (rest translated-dep)) ;; make sure each members occurs only once (unless (find candidate global-deps :test #'equal) (push candidate global-deps) (push `(,(to-symbol candidate) :type :system) members)))) translated-deps)) (unless parent-names ;; if this is a "top-level" system record its "external" ;; dependencies as well (if there are any) - don't do this ;; for "internal" modules as they may depend on files in the ;; containing system which can't be expressed in Common ;; Defsystem (when-let (load-deps (component-foreign-dependencies load-op module)) (push `(:in-order-to :load :all (:requires ,@(resolve-global-deps (translate-deps load-deps)))) rules)) (when-let (compile-deps (component-foreign-dependencies compile-op module)) (push `(:in-order-to :compile :all (:requires ,@(resolve-global-deps (translate-deps compile-deps)))) rules))) ;; loop through all components of the system (dolist (component (asdf:module-components module)) (let* ((input-files (asdf:input-files compile-op component)) (input-file (first input-files)) (component-name (asdf:component-name component))) (when (cdr input-files) (error "More than one input file for component ~S." component-name)) ;; first the requirement - note that we don't translate ;; the name here (as in NAME-TO-USE below) (when-let (load-deps (asdf::component-depends-on load-op component)) (push `(:in-order-to :load (,component-name) (:requires ,@(translate-deps load-deps))) rules)) (when-let (compile-deps (asdf::component-depends-on compile-op component)) (push `(:in-order-to :compile (,component-name) (:requires ,@(translate-deps compile-deps))) rules)) (etypecase component (asdf:system ;; an external system: just list it (push `(,(to-symbol component-name) :type :system) members)) (asdf:module ;; a module: list it but also create it as a Common ;; Defsystem system - this ain't really correct as a ;; module isn't a `stand-alone' system but I see no ;; better way to do it as LW can't do nested `modules' (let ((child-name (translate-module component (append parent-names (list module-name))))) (push `(,(to-symbol component-name :symbol child-name) :type :system) members))) ((or asdf:c-source-file asdf:cl-source-file) ;; a file: the tricky part is to get the name right (let* ((real-file-name (enough-namestring input-file module-pathname)) (file-type (or (pathname-type real-file-name) (asdf:source-file-type component module))) ;; use the Common Defsystem file types if possible (type (cond ((string-equal file-type "lisp") :lisp-file) ((string-equal file-type "lsp") :lsp-file) ((string-equal file-type "c") :c-file) (t nil))) ;; compute pathname of file from component name ;; like Common Defsystem would do it (path-computed-from-name (merge-pathnames (merge-pathnames (string component-name) (cond (type (make-pathname :type file-type)) (t (make-pathname)))) module-pathname)) ;; compute pathname of file from REAL-FILE-NAME ;; like Common Defsystem would do it (path-computed-from-file-name (merge-pathnames real-file-name module-pathname)) ;; decide which name to use for the component ;; based on some value of `elegance' - we want ;; it short if possible (name-to-use (cond ((equal input-file path-computed-from-name) component-name) ((equal input-file path-computed-from-file-name) (namestring real-file-name)) (t (namestring input-file))))) ;; if we couldn't use the component name itself we ;; have to register this conversion (unless (equal component-name name-to-use) (push (cons component-name (pathname-name* name-to-use)) substitutions)) ;; finally list it as a member (push `(,name-to-use :type ,(or type :lisp-file)) members)))))) (let ((module-name (cond (parent-names ;; if this module has parents then ;; construct an artifical name that ;; shows the heritage and tries to ;; make the module unique (make-unique-module-name module-name parent-names)) (t ;; otherwise just convert to symbol ;; without registering (to-symbol module-name :no-subs t))))) (eval `(defsystem ,module-name (:default-pathname ,module-pathname) :members ,(nreverse members) ;; now finally the substitutions :rules ,(nsublis substitutions (nreverse rules) :test #'equal))) ;; may be useful for large systems... (gc-if-needed) module-name))))) #-(or :lispworks8 :lispworks7) (defadvice (asdf::parse-component-form translate :around :documentation "Whenever an ASDF system is parsed we translate it to a Common Defsystem system definition on the fly.") (parent options) (let ((candidate (call-next-advice parent options))) (when (and *translate-asdf-systems* (typep candidate 'asdf:system)) (ignore-errors* (translate-module candidate))) candidate)) #-(or :lispworks8 :lispworks7) ;; translate the systems that have already been loaded (dolist (sys-name '(:cl-ppcre :cl-who :lw-doc :lw-add-ons)) (translate-module (asdf:find-system sys-name)))