;;;; -*- Mode:Common-Lisp; Package:MODULE-MANAGER; Syntax:common-lisp -*- ;;;; *-* File: /usr/local/gbbopen/source/module-manager/module-manager.lisp *-* ;;;; *-* Edited-By: cork *-* ;;;; *-* Last-Edit: Fri Jan 1 10:42:59 2010 *-* ;;;; *-* Machine: cyclone.cs.umass.edu *-* ;;;; ************************************************************************** ;;;; ************************************************************************** ;;;; * ;;;; * Module Manager Facility ;;;; * ;;;; ************************************************************************** ;;;; ************************************************************************** ;;; ;;; Written by: Dan Corkill (incorporating some original ideas by ;;; Kevin Gallagher and Zachary Rubinstein) ;;; ;;; Copyright (C) 2002-2010, Dan Corkill ;;; Part of the GBBopen Project (see LICENSE for license information). ;;; ;;; Porting Notice: ;;; ;;; The directory probing function, probe-directory, that is defined in ;;; this file must be extended when porting to a new CL implementation. ;;; ;;; -------------------------------------------------------------------------- ;;; ;;; This Module Manager Facility provides a lightweight and easy to use ;;; mechanism for maintaining (compiling and loading) module files. ;;; ;;; This file assumes the global variables *compiled-directory-name* and ;;; *compiled-file-type* have been defined by loading ;;; module-manager-loader.lisp. ;;; ;;; The Module Manager Facility supports the following directory layout: ;;; ;;; ;;; / \ ;;; / \ ;;; source ... ;;; / \ / \ ;;; / .. .. \ ;;; module-manager module-manager ;;; / \ ;;; / \ ;;; module-manager.lisp module-manager. ;;; ;;; This file can be used as a stand-alone system (when loaded by its ;;; companion file, module-manager-loader.lisp). Instructions for stand-alone ;;; usage of the Module Manager Facility are provided in the ;;; module-manager-startup.lisp file. ;;; ;;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ;;; ;;; 07-13-02 File created. (Corkill) ;;; 01-12-04 Added :create-dirs option to compile-module. (Corkill) ;;; 01-29-04 Exported MODULE-LOADED-P. (Corkill) ;;; 02-01-04 Support use of existing root-directory in DEFINE-ROOT-DIRECTORY. ;;; (Corkill) ;;; 03-19-04 Added top-level Module Manager commands for Lispworks. (Corkill) ;;; 03-19-04 Added file-options checking. (Corkill) ;;; 06-10-04 Added proper :forces-recompile date checking and warning ;;; messages. (Corkill) ;;; 06-11-04 Moved to separate package (for stand-alone use). (Corkill) ;;; 08-10-04 Removed MAKE-DIRECTORY in favor of ENSURE-DIRECTORIES-EXIST. ;;; (Corkill) ;;; 08-18-04 Add missing slot-definition documentation method for Digitool ;;; MCL. (Corkill) ;;; 02-06-05 Added LOAD-MODULE-FILE. (Corkill) ;;; 02-08-05 Added DESCRIBE-MODULE and BRIEF-DATE-AND-TIME. (Corkill) ;;; 05-22-05 Added ECL support. (Corkill) ;;; 06-08-05 Added CLISP support. (sds) ;;; 06-18-05 Added MODULE-DIRECTORIES. (Corkill) ;;; 02-13-06 Added GCL support. (Corkill) ;;; 04-11-06 Added *load-pathname* relative :directory option to ;;; DEFINE-MODULE. (Corkill) ;;; 05-08-06 Added support for the Scieneer CL. (dtc) ;;; 11-21-06 Added GET-DIRECTORY. (Corkill) ;;; 06-06-07 Added :after-form support for modules (somewhat reluctantly, ;;; as putting forms in a module's files is preferable to having ;;; them in the module definition). (Corkill) ;;; 07-14-07 Added subdirectories support to DEFINE-ROOT-DIRECTORY. (Corkill) ;;; 07-14-07 Added :noautorun compile/load-module option. (Corkill) ;;; 12-19-07 Added module-relative support to COMPUTE-RELATIVE-DIRECTORY and ;;; incremented Module Manager version to 1.2. (Corkill) ;;; 01-05-08 Skip undefined modules when performing compatiblity-ordering ;;; check of a module. (Corkill) ;;; 03-29-08 Added :nopropagate (:propagate canceling) compile/load-module ;;; option. (Corkill) ;;; 04-16-08 Support "Source" and "SOURCE" directory-name conventions (in ;;; addition to conventional "source"). (Corkill) ;;; 04-19-08 Added application-version-identifier support to ;;; DEFINE-ROOT-DIRECTORY and incremented version to 1.3. (Corkill) ;;; 05-15-08 Added PARSE-DATE. (Corkill) ;;; 06-23-08 Added BRIEF-DATE. (Corkill) ;;; 03-06-09 Added ending bounding-index second return value to PARSE-DATE. ;;; (Corkill) ;;; ;;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * (eval-when (:compile-toplevel :load-toplevel :execute) (unless (find-package ':module-manager) (error "This file should be loaded using the file ~ module-manager-loader.lisp"))) (in-package :module-manager) (eval-when (:compile-toplevel :load-toplevel :execute) (import '(#+xcl extensions:probe-directory))) ;;; --------------------------------------------------------------------------- ;;; Check if we are good to go: (flet ((check-var (var) (unless (boundp var) (error "~s is not defined.~ (This file should be loaded using the file ~ module-manager-loader.lisp)" var)))) (check-var '*compiled-directory-name*) (check-var '*compiled-file-type*)) ;;; =========================================================================== ;;; Allow-redefinition (placed here for very early use) (eval-when (:compile-toplevel :load-toplevel :execute) (defmacro allow-redefinition (&body body) ;;; Still need to support CMUCL, ECL, SBCL, and SCL `(#+allegro excl:without-redefinition-warnings #+clisp let #+clisp ((custom:*suppress-check-redefinition* 't)) #+clozure let #+clozure ((ccl:*warn-if-redefine* nil)) #+digitool-mcl let #+digitool-mcl ((ccl:*warn-if-redefine* nil)) #+lispworks system::without-warning-on-redefinition #-(or allegro clisp clozure digitool-mcl lispworks) progn (progn ,@body)))) ;;; =========================================================================== ;;; Imports to support using extended REPL commands: (eval-when (:compile-toplevel :load-toplevel :execute) (import '(common-lisp-user::*current-system-name* common-lisp-user::define-repl-command common-lisp-user::with-system-name))) (declaim (special *current-system-name*)) (unless (boundp '*current-system-name*) (setf *current-system-name* nil)) (unless (macro-function 'with-system-name) ;; This is a copy of the definition in ../../extended-repl.lisp. It is ;; needed to support startup.lisp only invocation. (allow-redefinition (defmacro with-system-name ((&optional system-name) &body body) (unless (keywordp system-name) (error "System name, ~s, must be a keyword." system-name)) `(let ((*current-system-name* ',system-name)) ,@body)))) ;;; =========================================================================== ;;; CL-User Global Variables ;;; ;;; Some CL implementations generate redefinition warnings when performing a ;;; compile/load/compile bootstrap sequence, so we don't use defvar's here to ;;; set default values. (eval-when (:compile-toplevel :load-toplevel :execute) (import '(common-lisp-user::*automatically-create-missing-directories* common-lisp-user::*autorun-modules* common-lisp-user::*module-manager-compile-verbose* common-lisp-user::*module-manager-load-verbose* common-lisp-user::*patches-only*))) ;;; --------------------------------------------------------------------------- ;;; Controls whether the Module Manager Facility automatically creates missing ;;; directories (without asking the user): (declaim (special *automatically-create-missing-directories*)) (unless (boundp '*automatically-create-missing-directories*) (setf *automatically-create-missing-directories* nil)) ;;; --------------------------------------------------------------------------- ;;; Controls whether the Module Manager Facility compiles/loads patches only: (declaim (special *patches-only*)) (unless (boundp '*patches-only*) (setf *patches-only* nil)) ;;; --------------------------------------------------------------------------- ;;; When true, the Module Manager Facility will generate its own compile & ;;; load messages if the corresponding *compile-verbose* or *load-verbose* ;;; values are nil. (declaim (special *module-manager-compile-verbose*)) (unless (boundp '*module-manager-compile-verbose*) (setf *module-manager-compile-verbose* nil)) (declaim (special *module-manager-load-verbose*)) (unless (boundp '*module-manager-load-verbose*) (setf *module-manager-load-verbose* nil)) ;;; --------------------------------------------------------------------------- ;;; Controls whether modules (such as GBBopen example and tests) autorun ;;; themselves. (declaim (special *autorun-modules*)) (unless (boundp '*autorun-modules*) (setf *autorun-modules* 't)) ;;; =========================================================================== ;;; Implementation-Specific Package & Feature Adjustments ;; Allow use of CMUCL package nicknames with SBCL: #+sbcl (eval-when (:compile-toplevel :load-toplevel :execute) (let ((fn (or ;; Pre SBCL-1.0.34: (find-symbol "ENTER-NEW-NICKNAMES" :sb-impl) ;; Post SBCL-1.0.34: (find-symbol "%ENTER-NEW-NICKNAMES" :sb-impl)))) (funcall fn (find-package "SB-PCL") '("PCL")) (funcall fn (find-package "SB-UNIX") '("UNIX")))) ;;; =========================================================================== ;;; Export user-level Module Manager names. (Some of these names could ;;; collide with similar names in other packages, but we export them anyway.) (eval-when (:compile-toplevel :load-toplevel :execute) (export '(*automatically-create-missing-directories* ; re-exported from ; :cl-user *autorun-modules* ; re-exported from :cl-user *current-module* ; not documented *current-system-name* ; re-exported from :cl-user *module-manager-compile-verbose* ; not yet documented *module-manager-load-verbose* ; not yet documented *month-precedes-date* ; part of tools, but placed here *patches-only* ; re-exported from :cl-user allow-redefinition ; part of tools, but placed here brief-date ; part of tools, but placed here brief-date-and-time ; part of tools, but placed here check-all-module-requires-orderings ; not yet documented compile-module compute-relative-directory ; not documented continue-patch define-relative-directory define-root-directory define-repl-command ; re-exported from :cl-user define-module describe-module describe-patches dotted-conc-name ; part of tools, but placed here; not ; documented finish-patch feature-present-p ; part of tools, but placed here; not ; documented freeze-module ; not yet documented get-directory get-patch-description get-root-directory ; not yet documented list-modules ; not yet documented load-module load-module-file module-manager-implementation-version ; not documented module-directories ; not yet documented module-loaded-p parse-date ; part of tools, but placed here patch patch-loaded-p printv ; part of tools, but placed here printv-printer ; part of tools, but placed here (not ; yet documented) show-defined-directories show-modules ; not yet documented start-patch undefine-directory ; not yet documented undefine-module ; not yet documented unfreeze-module ; not yet documented with-system-name ; re-exported from :cl-user with-module-redefinitions ; not yet documented ))) ;;; =========================================================================== (allow-redefinition (defun module-manager-implementation-version () "1.6")) ;;; Added to *features* at the end of this file: (defparameter *module-manager-version-keyword* ;; Support cross-case mode CLs: (read-from-string (format nil ":module-manager-~a" (module-manager-implementation-version)))) ;;; --------------------------------------------------------------------------- (allow-redefinition (defun print-module-manager-herald () (format t "~%;;; ~72,,,'-<-~> ;;; Module-Manager System ~a ;;; ;;; Developed and supported by the GBBopen Project (http:/GBBopen.org/) ;;; (See http://GBBopen.org/downloads/LICENSE for license details.) ;;; ~72,,,'-<-~>~2%" (module-manager-implementation-version))) (eval-when (:load-toplevel) (print-module-manager-herald))) ;;; =========================================================================== ;;; Add missing slot-definition documentation method to Digitool MCL: #+digitool-mcl (defmethod documentation ((object ccl::standard-slot-definition) &optional doc-type) (declare (ignore doc-type)) (when (and (slot-exists-p object 'documentation) (slot-boundp object 'documentation)) (slot-value object 'documentation))) ;;; =========================================================================== ;;; Printv ;;; ;;; A handy debugging macro ;;; ;;; Placed here to make this macro available ASAP ;;; ;;; --------------------------------------------------------------------------- ;;; NOTE: Copy any changes to PRINTV & PRINTV-PRINTER definitions to the ;;; stand-alone ../../printv.lisp file ;;; --------------------------------------------------------------------------- (defun printv-printer (forms forms-values-lists ;; Allow for customized printv-style printv'ers: &optional values-trans-fn) (let ((*print-readably* nil)) (loop for form in forms and form-values-list in forms-values-lists do (typecase form (keyword (format *trace-output* "~&;; ~s~%" form)) (string (format *trace-output* "~&;; ~a~%" form)) (t (format *trace-output* "~&;; ~w =>~{ ~w~^;~}~%" form (if values-trans-fn (funcall values-trans-fn form-values-list) form-values-list)))))) (force-output *trace-output*) (values-list (first (last forms-values-lists)))) ;;; --------------------------------------------------------------------------- (defmacro printv (&rest forms) (let ((forms-values-lists (gensym))) `(let ((,forms-values-lists (list ,.(mapcar #'(lambda (form) `(multiple-value-list ,form)) forms)))) (declare (dynamic-extent ,forms-values-lists)) (printv-printer ',forms ,forms-values-lists)))) ;;; =========================================================================== ;;; Feature-present-p and dotted-conc-name (eval-when (:compile-toplevel :load-toplevel :execute) (defun feature-present-p (feature-symbol) ;; Checks if :full-safety is on the *features* list (used at execution ;; time to conditionalize generated code, in place of read-time ;; conditionals) (member (symbol-name feature-symbol) *features* :test #'string=))) ;;; --------------------------------------------------------------------------- (defun dotted-conc-name (symbol) ;; Support reader-case-preserving CLs (concatenate 'simple-string (symbol-name symbol) ".")) ;;; =========================================================================== ;;; Basic declared numerics for use in this file (eval-when (:compile-toplevel :load-toplevel :execute) (defmacro & (arg) ;;; Wraps (the fixnum ...) around `arg' (if (feature-present-p ':full-safety) `,arg `(the fixnum ,arg))) (defmacro +& (&rest args) `(the fixnum (+ ,.(mapcar #'(lambda (x) `(the fixnum ,x)) args)))) (define-modify-macro incf& (&optional (increment 1)) +&)) ;;; =========================================================================== ;;; BRIEF-DATE, BRIEF-DATE-AND-TIME, and PARSE-DATE--part of the ;;; GBBopen-tools module. They are placed here to use with the :module-manager ;;; package. (defvar *month-precedes-date* 't) (defparameter *month-name-vector* #("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")) (defparameter *month-full-name-vector* #("January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December")) (defparameter *weekday-name-vector* #("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun")) (defparameter *weekday-full-name-vector* #("Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday")) ;;; --------------------------------------------------------------------------- (defun decode-supplied-universal-time (universal-time time-zone) (cond ((not universal-time) (setf universal-time (get-universal-time))) ;; User likely forgot the optional `universal-time' value: ((keywordp universal-time) (error "The optional universal-time value must be supplied when ~ specifying keyword options."))) (if time-zone (decode-universal-time universal-time time-zone) (decode-universal-time universal-time))) ;;; --------------------------------------------------------------------------- (locally ;; SBCL (rightly) complains about combining &optional and &key, but we ;; ignore that here: #+sbcl (declare (sb-ext:muffle-conditions style-warning)) (defun brief-date (&optional universal-time &key time-zone destination) ;;; Returns formatted date string (multiple-value-bind (second minute hour date month year) (decode-supplied-universal-time universal-time time-zone) (declare (ignore second minute hour)) (let ((month-name (svref (the (simple-array t (*)) *month-name-vector*) (& (1- (& month)))))) (if *month-precedes-date* (format destination "~a ~2d, ~s" month-name date year) (format destination "~2d ~a, ~s" date month-name year)))))) ;;; --------------------------------------------------------------------------- (locally ;; SBCL (rightly) complains about combining &optional and &key, but we ;; ignore that here: #+sbcl (declare (sb-ext:muffle-conditions style-warning)) (defun brief-date-and-time (&optional universal-time &key time-zone include-seconds destination) ;;; Returns formatted date/time string (brief, Unix ls-like form) (let ((current-time (get-universal-time)) time-difference) (if universal-time (setf time-difference (abs (- current-time universal-time))) (setf universal-time current-time time-difference 0)) (multiple-value-bind (second minute hour date month year) (decode-supplied-universal-time universal-time time-zone) (let ((month-name (svref (the (simple-array t (*)) *month-name-vector*) (& (1- (& month)))))) (if (< (& time-difference) ;; 120 days: #.(* 60 60 24 120)) (if *month-precedes-date* (format destination "~a ~2d ~2,'0d:~2,'0d~:[~;:~2,'0d~]" month-name date hour minute include-seconds second) (format destination "~2d ~a ~2,'0d:~2,'0d~:[~;:~2,'0d~]" date month-name hour minute include-seconds second)) (if *month-precedes-date* (format destination "~a ~2d, ~a~@[ ~]" month-name date year include-seconds) (format destination "~2d ~a, ~a~@[ ~]" date month-name year include-seconds)))))))) ;;; --------------------------------------------------------------------------- (defun parse-date (string &key (start 0) (end (length string)) (junk-allowed nil) (separators "-/ ,") (month-precedes-date *month-precedes-date*)) ;;; Parses many intuitive date formats (sensitive to month-precedes-date, ;;; if needed): (declare (simple-string string)) (let (date month year month-preceded-date name-equal-string) (labels ((name-equal (name) (when (string-equal string name :start1 start :end1 (& (min (& end) (& (+ (& start) (& (length name))))))) (setf name-equal-string name))) (process-date () (multiple-value-setq (date start) (parse-integer string :start start :end end :junk-allowed t))) (process-possible-day () (setf name-equal-string nil) (when (or (position-if #'name-equal *weekday-full-name-vector*) (position-if #'name-equal *weekday-name-vector*)) (incf& start (& (length name-equal-string))) (skip-separators))) (process-month () (cond ;; Numeric month: ((digit-char-p (schar string start)) (multiple-value-setq (month start) (parse-integer string :start start :end end :junk-allowed t))) ;; Month name: (t (setf name-equal-string nil) (setf month (or (position-if #'name-equal *month-full-name-vector*) (position-if #'name-equal *month-name-vector*))) (unless month (error "Unable to determine the month in ~s" string)) (incf& month) (incf& start (& (length name-equal-string)))))) (default-year () ;;; return the default year; used when a year is not specified: (multiple-value-bind (seconds minutes hours current-date current-month current-year) (get-decoded-time) (declare (ignore seconds minutes hours)) ;; Assume next year, if the date is past in the current year: (if (or (< (& month) (& current-month)) (and (= (& month) (& current-month)) (< (& date) (& current-date)))) (+& current-year 1) current-year))) (skip-separators () (loop while (and (< (& start) (& end)) (find (schar string start) separators)) do (incf& start)))) (skip-separators) ;; We might-have a day of week, which we skip: (process-possible-day) (when (< (the fixnum start) (the fixnum end)) ;; If we have a month name, then we know the month-date order; otherwise ;; we'll assume month-first for now, until we process the second field: (when (alpha-char-p (schar string start)) (setf month-preceded-date 't)) (process-month) (skip-separators) (cond ((and (not month-preceded-date) (or ;; We have a month name in the second field: (alpha-char-p (schar string start)) ;; Use the month-precedes-date value to decide the order: (not month-precedes-date))) ;; We actually have the date value from the first field (rather than ;; the month), so set the date from the assumed month value and then ;; proceed with month processing: (setf date month) (process-month)) ;; Simply continue, as we have month then date order: (t (process-date))) (skip-separators)) (check-type month (integer 1 12)) (check-type date (integer 1 31)) ;; Process year: (cond ;; Assumed year, if omitted: ((= (& start) (& end)) (setf year (default-year))) ;; Otherwise, process the specified year: (t (multiple-value-setq (year start) (parse-integer string :start start :end end :junk-allowed junk-allowed)) (if year ;; a year was specified: (check-type year (integer 0 #.most-positive-fixnum)) ;; use assumed year: (setf year (default-year))))) ;; Upgrade YY to YYYY -- YY assumed within +/- 50 years from current time ;; (if year < 100): (setf year (cond ;; No year upgrade needed: ((>= (& year) 100) year) ;; Do the upgrade: (t (let ((current-century (& (* 100 (& (truncate (& (nth-value 5 (get-decoded-time))) 100)))))) (if (>= (& year) 50) (& (+ (& year) (& current-century) -100)) (& (+ (& year) (& current-century))))))))) (values date month year start))) ;;; =========================================================================== ;;; Directories and modules hash tables (defvar *mm-directories* (make-hash-table :test 'eq)) (defvar *mm-modules* (make-hash-table)) ;;; =========================================================================== ;;; Module Directories (defstruct (mm-directory (:conc-name #.(dotted-conc-name 'mm-directory)) (:copier nil)) name (system-name *current-system-name*)) (defstruct (mm-root-directory (:include mm-directory) (:conc-name #.(dotted-conc-name 'mm-root-directory)) (:copier nil)) path application-version-modifier) (defstruct (mm-relative-directory (:include mm-directory) (:conc-name #.(dotted-conc-name 'mm-relative-directory)) (:copier nil)) root subdirectories) ;;; =========================================================================== ;;; Directory operators ;;; ;;; Unlike probe-file, probe-directory returns false on a non-directory file. ;;; It should return true for a symbolic link to a directory. (defun probe-directory (path) #+allegro (excl:file-directory-p path) #+clisp (ignore-errors ;; CLISP's probe-directory function signals an error if path is not a ;; directory: (ext:probe-directory path)) #+clozure (let ((pathname (probe-file path))) (and pathname (null (pathname-name pathname)) (null (pathname-type pathname)))) #+(and cmu unix) (let ((dir (namestring (make-pathname :name nil :type nil :defaults path)))) (eq (unix::unix-file-kind dir) :directory)) #+cormanlisp (cormanlisp:directory-p path) #+digitool-mcl (let ((pathname (probe-file path))) (and pathname (null (pathname-name pathname)) (null (pathname-type pathname)))) #+ecl (let ((pathname (probe-file path))) (and pathname (null (pathname-name pathname)) (null (pathname-type pathname)))) #+gcl ;; GCL's probe-file returns nil on directories, but directory returns ;; the directory (on linux, at least): (and (not (probe-file path)) (directory path)) #+lispworks (system::file-directory-p path) #+(and sbcl unix) (let ((dir (namestring (make-pathname :name nil :type nil :defaults path))) (fn (or (find-symbol "NATIVE-FILE-KIND" :sb-impl) ;; Pre-1.0.29 name for the above -- remove eventually: (find-symbol "UNIX-FILE-KIND" :sb-unix)))) (eq (funcall fn dir) :directory)) #+(and scl unix) (ext:unix-namestring (make-pathname :name nil :type nil :version nil :defaults path)) #-(or allegro clisp clozure (and cmu unix) cormanlisp digitool-mcl ecl gcl lispworks (and sbcl unix) (and scl unix) xcl) (need-to-port probe-directory)) ;;; --------------------------------------------------------------------------- (defun non-keyword-directory-name-error (name) (error "Directory name, ~s, must be a keyword." name)) ;;; --------------------------------------------------------------------------- (defun non-keyword-root/relative-directory-name-error (name) (error "Root or relative directory name, ~s, must be a keyword." name)) ;;; --------------------------------------------------------------------------- (defun get-mm-root-directory (name) (let ((mm-dir (gethash name *mm-directories*))) (typecase mm-dir (mm-root-directory mm-dir) (t (error "Root directory ~s is not defined." name))))) ;;; --------------------------------------------------------------------------- (defun append-subdirectories (directory &rest subdirectory-lists) ;; Process pathname-directory :up keywords ourselves, to keep things pretty ;; on CLs that don't normalize aggressively (we'll leave :back ;; keywords--should anyone use them--to CL to deal with): (declare (dynamic-extent subdirectory-lists)) (let ((directory (reverse directory))) (dolist (subdirectories subdirectory-lists) (dolist (subdirectory subdirectories) (cond ((and (eq subdirectory ':up) (stringp (first directory))) (pop directory)) (t (push subdirectory directory))))) (nreverse directory))) ;;; --------------------------------------------------------------------------- (defun compute-root-directory (spec subdirectories) (flet ((compute-it (spec) (let ((root-pathname (etypecase spec (pathname (make-pathname :name nil :type nil :defaults spec)) (string (pathname spec)) (mm-root-directory (mm-root-directory.path spec))))) (make-pathname :directory (append-subdirectories (pathname-directory root-pathname) subdirectories) :defaults root-pathname)))) (typecase spec (symbol (compute-it (if (keywordp spec) (get-mm-root-directory spec) (symbol-value spec)))) (otherwise (compute-it spec))))) ;;; --------------------------------------------------------------------------- (defun define-root-directory (name spec &rest subdirectories) (declare (dynamic-extent subdirectories)) (let ((application-version-modifier nil)) (when (consp name) (setf application-version-modifier (second name)) (setf name (first name))) (unless (keywordp name) (non-keyword-directory-name-error name)) (let ((root-directory-path (compute-root-directory spec subdirectories))) (setf (gethash name *mm-directories*) (make-mm-root-directory :name name :path root-directory-path :application-version-modifier application-version-modifier))))) ;;; --------------------------------------------------------------------------- (defun define-relative-directory (name root &rest subdirectories) (unless (keywordp name) (non-keyword-directory-name-error name)) (unless (keywordp root) (non-keyword-root/relative-directory-name-error root)) (setf (gethash name *mm-directories*) (make-mm-relative-directory :name name :root root :subdirectories subdirectories))) ;;; --------------------------------------------------------------------------- (defun make-and-check-directory-pathname (name subdirectories compiled? application-version-modifier patches?) ;;; Used by compute-relative-directory to handle various "source" ;;; directory-name conventions: (labels ((make-directory-pathname (subtree-name &optional skip-subdirectories?) (make-pathname :directory (append-subdirectories (pathname-directory name) (list subtree-name) (unless skip-subdirectories? (if patches? (append subdirectories '("patches")) subdirectories))) :defaults name))) (cond ;; If compiled?, make and return the pathname (concatenating ;; application-version modifier, if appropriate): (compiled? (make-directory-pathname (if application-version-modifier ;; Concatenate the version modifier to the compiled-directory name: (concatenate 'simple-string *compiled-directory-name* "-" application-version-modifier) *compiled-directory-name*))) ;; Source directory: check what "source" directory name is needed before ;; making and returning the pathname: (t (cond ;; Regular "source": ((probe-directory (make-directory-pathname "source" t)) (make-directory-pathname "source")) ;; "Source": ((probe-directory (make-directory-pathname "Source" t)) (make-directory-pathname "Source")) ;; "SOURCE": ((probe-directory (make-directory-pathname "SOURCE" t)) (make-directory-pathname "SOURCE")) ;; Otherwise, we'll just use "source": (t (make-directory-pathname "source"))))))) ;;; --------------------------------------------------------------------------- ;; CMUCL 19e and SCL complain about the following declaration: #-(or cmu scl) (declaim (ftype (function (mm-module) (values t &optional)) mm-module.directory mm-module.subdirectories)) (defun compute-relative-directory (name subdirectories compiled? &optional patches?) (let ((in-process nil)) (labels ((compute-it (name subdirectories) (cond ((null name) nil) ;; `Name' can be a pathname if a *load-truename*-relative ;; :directory option was used in define-module: ((pathnamep name) (make-and-check-directory-pathname name subdirectories compiled? nil patches?)) (t (let ((mm-dir (gethash name *mm-directories*))) (typecase mm-dir (mm-relative-directory (when (member name in-process :test #'eq) (error "Circularity in relative-directory relation:~ ~%~3t~{~s -> ~}~s" (reverse in-process) name)) (push name in-process) (compute-it (mm-relative-directory.root mm-dir) (append-subdirectories (mm-relative-directory.subdirectories mm-dir) subdirectories))) (mm-root-directory (let ((root-path (mm-root-directory.path mm-dir)) (application-version-modifier (mm-root-directory.application-version-modifier mm-dir))) (make-and-check-directory-pathname root-path subdirectories compiled? application-version-modifier patches?))) (otherwise (let ((module ;; Check if we have a module reference (look ;; without the get-module error check): (gethash name *mm-modules*))) (cond ;; The reference is module relative: (module (when (eq name (mm-module.directory module)) (error "Directory ~s is defined in terms of itself" name)) (compute-relative-directory (mm-module.directory module) (append-subdirectories (mm-module.subdirectories module) subdirectories) compiled? patches?)) (t (error "Directory ~s is not defined." name))))))))))) (compute-it name subdirectories)))) ;;; --------------------------------------------------------------------------- (defun undefine-directory (name) (if (gethash name *mm-directories*) (remhash name *mm-directories*) (warn "Directory ~s is not defined." name))) ;;; --------------------------------------------------------------------------- (defun show-defined-directories () (cond ((zerop (the fixnum (hash-table-count *mm-directories*))) (format t "~& No directories are defined.~%")) (t (let ((directories nil)) (maphash #'(lambda (key directory) (declare (ignore key)) (push directory directories)) *mm-directories*) (dolist (directory (sort directories #'string-lessp :key #'mm-directory.name)) (typecase directory (mm-root-directory (format t "~&~s~%~4tRoot: ~a" (mm-directory.name directory) (mm-root-directory.path directory))) (t (let ((root-name (mm-relative-directory.root directory))) (format t "~&~s~%~4tRelative to~:[ module~;~] ~(~s~)~ ~%~4tsubdirectories: ~s" (mm-directory.name directory) (gethash root-name *mm-directories*) root-name (mm-relative-directory.subdirectories directory)))))) (terpri)))) (terpri) (values)) ;;; =========================================================================== ;;; Modules (defvar *skip-requires-ordering-check* nil) (defvar *deferred-requires-ordering-check-module-names* nil) ;;; --------------------------------------------------------------------------- (defstruct (mm-module (:conc-name #.(dotted-conc-name 'mm-module)) (:copier nil)) ;;; NOTE: Changes to slots must be reflected in ENSURE-MODULE: name (directory nil) (subdirectories) (requires nil) (files nil) (files-loaded nil) (patches nil) (load-completed? nil) (latest-forces-recompiled-date 0) ;; undocumented (used for compile-gbbopen exit): (after-form nil) (system-name *current-system-name*) (patch-descriptions nil) (frozen? nil)) ;;; --------------------------------------------------------------------------- (defmethod print-object ((object mm-module) stream) (cond (*print-readably* (call-next-method)) (t (print-unreadable-object (object stream :type t) (format stream "~:[~;% ~]~s" (mm-module.frozen? object) (mm-module.name object))) ;; Print-object must return object: object))) ;;; --------------------------------------------------------------------------- (defmacro with-module-redefinitions (&body body) ;; skip requires-ordering checks as we go: `(let ((*skip-requires-ordering-check* 't) (*deferred-requires-ordering-check-module-names* nil)) ,@body ;; but do check them at the end: (check-all-module-requires-orderings :module-names ;; Check them in "as seen" order: (nreverse *deferred-requires-ordering-check-module-names*)))) ;;; --------------------------------------------------------------------------- (defmacro define-module (name &body args) (unless (keywordp name) (error "Module name, ~s, must be a keyword." name)) (let ((directory nil) (directory-seen? nil) (subdirectories nil) (requires nil) (requires-seen? nil) (files nil) (files-seen? nil) (patches nil) (patches-seen? nil) (after-form nil) (after-form-seen? nil)) (dolist (option args) (unless (and (consp option) (keywordp (first option))) (error "Badly formed option, ~s, in module ~s.~_~ Each option must be a list of the form (