Skip to content
normalize-dependency.lisp 10.8 KiB
Newer Older
#+xcvb (module (:depends-on ("names" "grain-interface")))

(in-package :xcvb)

;;; Recognizer for current trivial dependency language

(defvar *asdf-systems-warned* ()
  ;; This is a bit of a kluge, but oh well.
  "the names of ASDF systems for which we have already issued a warning that
a reference to the system was superseded by a build.xcvb file.")

(defun lisp-module-grain-from (name grain)
  (let ((lisp-module-grain (resolve-module-name name grain)))
    (unless (lisp-module-grain-p lisp-module-grain)
      (error "Couldn't resolve ~S to a valid module from grain ~S~@[ pathname ~S~]"
             name (fullname grain) (grain-pathname grain)))

(defun lisp-fullname-from (name grain)
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
  (second (fullname (lisp-module-grain-from name grain))))

(defun unrecognized-dependency (dep)
  (error "unrecognized dependency ~S" dep))

(defun normalize-dependencies (grain deps type)
    (error "In module ~S~@[ pathname ~S~], ~S dependencies are not a list but ~S"
           (fullname grain) (grain-pathname grain) type deps))
  (mapcar/ #'normalize-dependency grain deps))

(defun normalize-dependency (grain dep)
  (normalize-dependency-dispatcher grain dep))

(define-simple-dispatcher normalize-dependency #'normalize-dependency-atom)

(defun normalize-dependency-atom (grain name)
  (check-type grain grain)
  (check-type name string)
  (let ((g (resolve-module-name name grain)))
      (null (error "~@<Failed to resolve name ~S from grain ~S~@[ pathname ~S~]~:>"
		   name (fullname grain) (grain-pathname grain)))
      (executable-grain `(:executable ,(fullname g)))
      (build-module-grain `(:build ,(fullname g)))
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
      (lisp-file-grain `(:fasl ,(second (fullname g)))))))

(define-normalize-dependency :when (grain expression &rest dependencies)
  ;; TODO: parse and make sure that expression is well-formed, which
  ;; should issue an error message early if there user-provided code is wrong.
  `(:when ,expression ,@(normalize-dependencies grain dependencies :when)))

(define-normalize-dependency :cond (grain &rest cond-expressions)
  ;; TODO: parse and make sure that expression is well-formed, which
  ;; should issue an error message early if there user-provided code is wrong.
  `(:cond ,@(mapcar (lambda (x) (cons (car x) (normalize-dependencies grain (cdr x) :cond)))
                    cond-expressions)))

(defun normalize-dependency-lisp* (type grain name)
  `(,type ,(lisp-fullname-from name grain)))
(define-normalize-dependency :lisp (grain name)
  (normalize-dependency-lisp* :lisp grain name))
(define-normalize-dependency :fasl (grain name)
  (normalize-dependency-lisp* :fasl grain name))
(define-normalize-dependency :cfasl (grain name)
  (normalize-dependency-lisp* :cfasl grain name))
(define-normalize-dependency :lisp-object (grain name)
  (normalize-dependency-lisp* :lisp-object grain name))

(defun normalize-dependency-build* (type grain name)
  (let ((g (lisp-module-grain-from name grain)))
    (check-type g lisp-module-grain)
    `(,type ,(fullname g))))

(define-normalize-dependency :build (grain name)
  (normalize-dependency-build* :build grain name))
(define-normalize-dependency :compile-build (grain name)
  (normalize-dependency-build* :compile-build grain name))

(define-normalize-dependency :compile (grain name)
  (let ((g (lisp-module-grain-from name grain)))
    (check-type g lisp-module-grain)
        `(:compile-build ,n)
        `(,(compile-time-fasl-type) ,n)))))

(define-normalize-dependency :asdf (grain name)
  (declare (ignore grain))
  (let* ((n (coerce-asdf-system-name name))
         (superseding (registered-build `(:supersedes-asdf ,n))))
    (etypecase superseding
      (null
       `(:asdf ,n))
       (let ((nn (second (assoc n (asdf-supersessions superseding) :test 'equal))))
         (unless (member n *asdf-systems-warned* :test 'equal)
           (push n *asdf-systems-warned*))
         (normalize-dependency-atom superseding nn)))
      (require-grain
       (let ((nn (fullname superseding)))
         (unless (member nn *asdf-systems-warned* :test 'equal)
           (push nn *asdf-systems-warned*))
      (invalid-build-registry-entry
       (error "Trying to use ASDF system :~A claimed by invalid build ~S"
              n superseding)))))

(define-normalize-dependency :require (grain name)
  (declare (ignore grain))
  (check-type name (or string symbol))
  `(:require ,(intern (string name) :keyword)))

(define-normalize-dependency :source (grain name &key in)
  "File named relatively to a build"
  (let ((path (portable-pathname-from-string name)))
    (if (absolute-pathname-p path)
        (multiple-value-bind (build suffix)
            (resolve-build-relative-name name)
          (if build
              `(:source ,suffix :in ,(fullname build))
              (error "Couldn't find in a build to which ~S is relative" name)))
        (let ((build (if in
                         (registered-build (canonicalize-fullname in) :ensure-build t)
                         (build-module-grain-for grain))))
        `(:source ,name :in ,(fullname build))))))

(define-normalize-dependency :object (grain name)
  "File named relatively to the object directory"
  `(:object
    ,(portable-namestring
       (portable-pathname-from-string name)
       (portable-pathname-from-string (fullname grain))))))

(define-normalize-dependency :file (grain name)
  "File named relatively to the filesystem"
  `(:file ,(namestring
            (ensure-absolute-pathname
              name
              (pathname-directory-pathname
               (grain-pathname
                (build-module-grain-for grain))))))))

;;; Matcher for the normalized dependency language
(defparameter +dependency-type+
    (:fasl . fasl-grain)
    (:cfasl . cfasl-grain)
    (:executable . executable-grain)
    (:dynamic-library . dynamic-library-grain)
    (:static-library . static-library-grain)
    (:image . image-grain)
    (:asdf . asdf-grain)
    (:require . t)
    (:build . t)
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
    (:compile-build . t)
    (:source . t)
    (:object . t)
    (:file . t))
  "what type for grains corresponding to a given dependency tag")

(defparameter +computing-dependencies+
  '(:when :cond :source :require))

(defun deconstruct-dependency (dep k)
  (flet ((err () (error "malformed dependency ~S" dep)))
    (typecase dep
      (cons
         (let* ((head (first dep))
                (computing (and (member head +computing-dependencies+) t)))
           (if computing
             (funcall k head nil t)
             (progn
               (unless (and (list-of-length-p 2 dep)
                            (stringp (second dep)))
                 (err))
               (let* ((name (second dep))
                      (type (or computing (cdr (assoc head +dependency-type+)))))
                 (unless (or computing type)
                   (err))
                 (funcall k head name type))))))
      (string
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
         (funcall k :lisp dep 'lisp-file-grain))
      (t
         (err)))))

(defmacro with-dependency ((&key head name type) expr &body body)
  (loop :for v :in (list head name type)
        :for var = (or v (gensym))
        :collect var :into vars
        :unless v :collect var :into ignored
        :finally (return
                   `(deconstruct-dependency
                     ,expr
                     (lambda ,vars
                       ,@(when ignored `((declare (ignore ,@ignored))))
                       ,@body)))))

(defun compiled-dependency (dep)
  "Go from a load-time dependency to the corresponding compile-time dependency,
in the normalized dependency mini-language"
  (compiled-dependency-dispatcher dep))
(define-simple-dispatcher compiled-dependency #'unrecognized-dependency :environment nil)
(define-compiled-dependency :fasl (x)
  (list (compile-time-fasl-type) x))
(define-compiled-dependency :build (x)
(macrolet ((d (k) `(define-compiled-dependency ,k (&rest r) (cons ,k r)))
           (self-compiled-dependency (&rest r) `(progn ,@(loop :for k :in r :collect `(d ,k)))))
  (self-compiled-dependency :source :lisp :cfasl :lisp-object :asdf :require
			    :compile-build :static-library :dynamic-library :executable))
(define-compiled-dependency :when (c &rest deps)
  `(:when ,c ,@(mapcar #'compiled-dependency deps)))
(define-compiled-dependency :cond (&rest clauses)
  `(:cond ,@(loop :for (c . deps) :in clauses
              :collect (cons c (mapcar #'compiled-dependency deps)))))
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
(defun linkable-dependency (dep)
  "Go from a load-time dependency to the corresponding compile-time dependency,
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
in the normalized dependency mini-language"
  (linkable-dependency-dispatcher dep))
(define-simple-dispatcher linkable-dependency #'unrecognized-dependency :environment nil)
(define-linkable-dependency :cfasl (x) `(:lisp-object ,x))
(define-linkable-dependency :fasl (x) `(:lisp-object ,x))
(define-linkable-dependency :lisp-object (x) `(:lisp-object ,x))
(define-linkable-dependency :dynamic-library (x) `(:static-library ,x))
(define-linkable-dependency :compile-build (x) `(:build ,x))
(macrolet ((d (k) `(define-linkable-dependency ,k (&rest r) (cons ,k r)))
           (self-linkable-dependency (&rest r) `(progn ,@(loop :for k :in r :collect `(d ,k)))))
  (self-linkable-dependency :asdf :require :build :static-library))
(define-linkable-dependency :when (c &rest deps)
  `(:when ,c ,@(mapcar #'linkable-dependency deps)))
(define-linkable-dependency :cond (&rest clauses)
  `(:cond ,@(loop :for (c . deps) :in clauses
              :collect (cons c (mapcar #'linkable-dependency deps)))))
(defun compile-time-fasl-type ()
  (if *use-cfasls* :cfasl :fasl))

(defun fasl-grains-for-name (env fullname
                             load-dependencies cload-dependencies build-dependencies)
  (flet ((m (class kw name deps &aux (fullname `(,kw ,(second name))))
            :fullname fullname
            :vp (default-vp-for-fullname env fullname)
            :load-dependencies deps)))
    `(,(m 'fasl-grain :fasl fullname (append build-dependencies load-dependencies))
      ,@(cond
	 ((target-ecl-p)
	  `(,(m 'lisp-object-grain :lisp-object fullname
			      (append build-dependencies cload-dependencies))))
         (*use-cfasls*
	  `(,(m 'cfasl-grain :cfasl fullname
		(append build-dependencies cload-dependencies))))))))

(defun cfasl-for-fasl (fasl-grain)
  (check-type fasl-grain fasl-grain)
  (if *use-cfasls*
    (second (computation-outputs (grain-computation fasl-grain)))
    fasl-grain))
    ((or fasl-grain cfasl-grain lisp-object-grain)
     (registered-grain `(:lisp (second (fullname grain)))))
    (t
     nil)))

(defun fullname-source (fullname)
  (let ((g (grain-source (registered-grain fullname))))
    (when g (fullname g))))