git.fiddlerwoaroof.com
support/compile.scm
4e987026
 ;;; compile.scm -- compilation utilities
 ;;;
 ;;; author :  Sandra Loosemore
 ;;; date   :  24 Oct 1991
 ;;;
 ;;; This file defines a makefile-like compilation system that supports
 ;;; a hierarchy of dependencies.
 ;;; The external entry points are define-compilation-unit, load-unit, and
 ;;; compile-and-load-unit.
 
 
 
 ;;;=====================================================================
 ;;; Parsing
 ;;;=====================================================================
 
 
 ;;; Establish global defaults for filenames.
 
 (define compile.source-filename source-file-type)
 (define compile.binary-filename binary-file-type)
 (define compile.binary-subdir (string-append lisp-implementation-name "/"))
 (define compile.delayed-loads '())
 
 
 ;;; Top level units are stored in this table.
 ;;; This is really a slight wart on the whole scheme of things; this
 ;;; is done instead of storing the top-level units in variables because
 ;;; we were getting unintentional name collisions.
 
 (define compile.unit-table (make-table))
 
 (define-syntax (compile.lookup-unit name)
   `(table-entry compile.unit-table ,name))
 
 (define (mung-global-units names lexical-units)
   (map (lambda (n)
 	 (if (memq n lexical-units)
 	     n
 	     `(compile.lookup-unit ',n)))
        names))
 
 
 ;;; Top-level compilation units are defined with define-compilation-unit.
 ;;; The body can consist of the following clauses:
 ;;;
 ;;; (source-filename <filename>)
 ;;; (binary-filename <filename>)
 ;;;   Specify source and/or binary file names.  For nested units, these
 ;;;   are merged with defaults from outer units.  If you don't specify
 ;;;   an explicit binary filename, it's inherited from the source file
 ;;;   name.
 ;;; (require ...)
 ;;;   Specify compile/load dependencies.  Arguments are names of other
 ;;;   units/component files; these names have scoping like let*, so a unit
 ;;;   can require previously listed units at the same or outer level.
 ;;; (unit name ....)
 ;;;   Specifies a nested unit.  This can appear multiple times.
 ;;;   If a unit doesn't include any nested units, then it's a leaf
 ;;;   consisting of a single source file.
 ;;; (load <boolean>)
 ;;;   If supplied and false, the unit isn't loaded unless it is needed
 ;;;   to satisfy a require clause.  Used for files containing compilation
 ;;;   support stuff.
 ;;; (compile <boolean>)
 ;;;   If supplied and false, the unit isn't compiled.  Only useful for
 ;;;   leaf nodes.  Typically used in combination with (load '#f) to suppress
 ;;;   compilation of stuff only used at compile time.
 
 (define-syntax (define-compilation-unit name . clauses)
   `(begin
      (let ((unit  ,(compile.process-unit-spec name clauses '#t '())))
        (setf (compile.lookup-unit ',name) unit)
        (setf compilation-units (append compilation-units (list unit))))
      ',name))
 
 
 ;;; The basic approach is to turn the compilation unit definition into
 ;;; a big LET*, and put calls to build the actual unit object inside
 ;;; of this.
 ;;; 
 
 (define (compile.process-unit-spec name clauses top-level? lexical-units)
   (multiple-value-bind
       (source-filename binary-filename require nested-units
 		       load? compile?)
       (compile.parse-unit-spec clauses lexical-units)
     `(let* ((compile.source-filename ,source-filename)
 	    (compile.binary-filename ,binary-filename)
 	    (compile.unit-require    (list ,@require))
 	    (compile.delayed-loads   (append compile.delayed-loads
 					     (compile.select-delayed-loads
 						     compile.unit-require)))
 	    ,@nested-units)
        (make compile.unit
 	     (name ',name)
 	     (source-filename compile.source-filename)
 	     (binary-filename compile.binary-filename)
 	     (components (list ,@(map (function car) nested-units)))
 	     (require compile.unit-require)
 	     (top-level? ',top-level?)
 	     (load? ,load?)
 	     (compile? ,compile?)
 	     (delayed-loads compile.delayed-loads)))))
 
 (define (compile.parse-unit-spec clauses lexical-units)
   (let ((source-filename  '#f)
 	(binary-filename  '#f)
 	(require          '#f)
 	(nested-units     '())
 	(load?            ''#t)
 	(compile?         ''#t))
     (dolist (c clauses)
       (cond ((not (pair? c))
 	     (compile.unit-syntax-error c))
 	    ((eq? (car c) 'source-filename)
 	     (if source-filename
 		 (compile.unit-duplicate-error c)
 		 (setf source-filename (cadr c))))
 	    ((eq? (car c) 'binary-filename)
 	     (if binary-filename
 		 (compile.unit-duplicate-error c)
 		 (setf binary-filename (cadr c))))
 	    ((eq? (car c) 'require)
 	     (if require
 		 (compile.unit-duplicate-error c)
 		 (setf require (mung-global-units (cdr c) lexical-units))))
 	    ((eq? (car c) 'unit)
 	     (push (list (cadr c)
 			 (compile.process-unit-spec (cadr c) (cddr c)
 						    '#f lexical-units))
 		   nested-units)
 	     (push (cadr c) lexical-units))
 	    ((eq? (car c) 'load)
 	     (setf load? (cadr c)))
 	    ((eq? (car c) 'compile)
 	     (setf compile? (cadr c)))
 	    (else
 	     (compile.unit-syntax-error c))))
     (values
         (if source-filename
 	    `(compile.merge-filenames ,source-filename
 		     compile.source-filename '#f)
 	    'compile.source-filename)
 	(if binary-filename
 	    `(compile.merge-filenames ,binary-filename
 		     compile.binary-filename '#f)
 	    (if source-filename
 		'(compile.merge-filenames compile.binary-filename
 			 compile.source-filename
 			 compile.binary-subdir)
 		'compile.binary-filename))
 	(or require '())
 	(nreverse nested-units)
 	load?
 	compile?)))
 
 
 (predefine (error format . args))
 
 (define (compile.unit-syntax-error c)
   (error "Invalid compilation unit clause ~s." c))
 
 (define (compile.unit-duplicate-error c)
   (error "Duplicate compilation unit clause ~s." c))
 
 
 
 ;;;=====================================================================
 ;;; Representation and utilities
 ;;;=====================================================================
 
 ;;; Here are constructors and accessors for unit objects.
 ;;; Implementationally, the compilation unit has the following slots:
 ;;;
 ;;; * The unit name.
 ;;; * The source file name.
 ;;; * The binary file name.
 ;;; * A list of component file/units.
 ;;; * A list of units/files to require.
 ;;; * A load timestamp.
 ;;; * A timestamp to keep track of the newest source file.
 ;;; * Flags for compile and load.
 
 (define-struct compile.unit
   (predicate compile.unit?)
   (slots
     (name             (type symbol))
     (source-filename  (type string))
     (binary-filename  (type string))
     (components       (type list))
     (require          (type list))
     (top-level?       (type bool))
     (load?            (type bool))
     (compile?         (type bool))
     (delayed-loads    (type list))
     (load-time        (type (maybe integer)) (default '#f))
     (source-time      (type (maybe integer)) (default '#f))
     (last-update      (type (maybe integer)) (default 0))
     ))
 
 (define (compile.newer? t1 t2)
   (and t1
        t2
        (> t1 t2)))
 
 (define (compile.select-newest t1 t2)
   (if (compile.newer? t1 t2) t1 t2))
 
 (define (compile.get-source-time u)
   (let ((source-file  (compile.unit-source-filename u)))
     (if (file-exists? source-file)
 	(file-write-date source-file)
 	'#f)))
 
 (define (compile.get-binary-time u)
   (let ((binary-file  (compile.unit-binary-filename u)))
     (if (file-exists? binary-file)
 	(file-write-date binary-file)
 	'#f)))
 
 (define (compile.load-source u)
   (load (compile.unit-source-filename u))
   (setf (compile.unit-load-time u) (current-date)))
 
 (define (compile.load-binary u)
   (load (compile.unit-binary-filename u))
   (setf (compile.unit-load-time u) (current-date)))
 
 (define (compile.compile-and-load u)
   (let ((source-file  (compile.unit-source-filename u))
 	(binary-file  (compile.unit-binary-filename u)))
     (compile-file source-file binary-file)
     (load binary-file)
     (setf (compile.unit-load-time u) (current-date))))
 
 (define (compile.do-nothing u)
   u)
 
       
 ;;;=====================================================================
 ;;; Runtime support for define-compilation-unit
 ;;;=====================================================================
 
 (define (compile.select-delayed-loads require)
   (let ((result  '()))
     (dolist (r require)
       (if (not (compile.unit-load? r))
 	  (push r result)))
     (nreverse result)))
 
 (define (compile.merge-filenames fname1 fname2 add-subdir)
   (let ((place1  (filename-place fname1))
 	(name1   (filename-name fname1))
 	(type1   (filename-type fname1)))
     (assemble-filename
         (if (string=? place1 "")
 	    (if add-subdir
 		(string-append (filename-place fname2) add-subdir)
 		fname2)
 	    place1)
 	(if (string=? name1 "") fname2 name1)
 	(if (string=? type1 "") fname2 type1))))
 
 
 
 ;;;=====================================================================
 ;;; Load operation
 ;;;=====================================================================
 
 ;;; Load-unit and compile-and-load-unit are almost identical.  The only 
 ;;; difference is that load-unit will load source files as necessary, while
 ;;; compile-and-load-unit will compile them and load binaries instead.
 
 (define (load-unit u)
   (compile.update-unit-source-times u '#f (current-date))
   (compile.load-unit-aux u))
 
 (define (compile.load-unit-aux u)
   (with-compilation-unit ()
     (compile.load-unit-recursive u '#f)))
 
 (define (compile-and-load-unit u)
   (compile.update-unit-source-times u '#f (current-date))
   (compile.compile-and-load-unit-aux u))
 
 (define (compile.compile-and-load-unit-aux u)
   (with-compilation-unit ()
     (compile.load-unit-recursive u '#t)))
 
 
 ;;; Load a bunch of compilation units as a group.  This is useful because
 ;;; it can prevent repeated lookups of file timestamps.  Basically, the
 ;;; assumption is that none of the source files will change while the loading
 ;;; is in progress.
 ;;; In case of an error, store the units left to be compiled in a global
 ;;; variable.
 
 (define remaining-units '())
 
 (define (load-unit-list l)
   (let ((timestamp  (current-date)))
     (dolist (u l)
       (compile.update-unit-source-times u '#f timestamp))
     (setf remaining-units l)
     (dolist (u l)
       (compile.load-unit-aux u)
       (pop remaining-units))))
 
 (define (compile-and-load-unit-list l)
   (let ((timestamp  (current-date)))
     (dolist (u l)
       (compile.update-unit-source-times u '#f timestamp))
     (setf remaining-units l)
     (dolist (u l)
       (compile.compile-and-load-unit-aux u)
       (pop remaining-units))))
 
 
 ;;; Walk the compilation unit, updating the source timestamps.
 
 (define (compile.update-unit-source-times u newest-require timestamp)
   (unless (eqv? timestamp (compile.unit-last-update u))
     (setf (compile.unit-last-update u) timestamp)
     (dolist (r (compile.unit-require u))
       (if (compile.unit-top-level? r)
 	  (compile.update-unit-source-times r '#f timestamp))
       (setf newest-require
 	    (compile.select-newest newest-require
 				   (compile.unit-source-time r))))
     (let ((components  (compile.unit-components u)))
       (if (not (null? components))
 	  (let ((source-time  newest-require))
 	    (dolist (c components)
 	      (compile.update-unit-source-times c newest-require timestamp)
 	      (setf source-time
 		    (compile.select-newest source-time
 					   (compile.unit-source-time c))))
 	    (setf (compile.unit-source-time u) source-time))
 	  (setf (compile.unit-source-time u)
 		(compile.select-newest
 		  newest-require
 		  (compile.get-source-time u)))))))
 
 
 ;;; Load a compilation unit.  Do this by first loading its require list,
 ;;; then by recursively loading each of its components, in sequence.  
 ;;; Note that because of the way scoping of units works and the
 ;;; sequential nature of the load operation, only top-level
 ;;; units in the require list have to be loaded explicitly.
 
 (define (compile.load-unit-recursive u compile?)
   (let ((components       (compile.unit-components u)))
     ;; First recursively load dependencies.
     ;; No need to update time stamps again here.
     (dolist (r (compile.unit-require u))
       (if (compile.unit-top-level? r)
 	  (compile.load-unit-aux r)))
     (if (not (null? components))
 	;; Now recursively load subunits.
 	(dolist (c components)
 	  (unless (not (compile.unit-load? c))
 	    (compile.load-unit-recursive c compile?)))
 	;; For a leaf node, load either source or binary if necessary.
 	(let ((source-time  (compile.unit-source-time u))
 	      (binary-time  (compile.get-binary-time u))
 	      (load-time    (compile.unit-load-time u)))
 	  (cond ((compile.newer? load-time source-time)
 		 ;; The module has been loaded since it was last changed,
 		 ;; but maybe we want to compile it now.
 		 (if (and compile?
 			  (compile.unit-compile? u)
 			  (compile.newer? source-time binary-time))
 		     (begin
 		       (compile.do-delayed-loads
 			       (compile.unit-delayed-loads u)
 			       compile?)
 		       (compile.compile-and-load u))
 		     (compile.do-nothing u)))
 		((compile.newer? binary-time source-time)
 		 ;; The binary is up-to-date, so load it.
 		 (compile.load-binary u))
 		(else
 		 ;; The binary is out-of-date, so either load source or
 		 ;; recompile the binary.
 		 (compile.do-delayed-loads
 			 (compile.unit-delayed-loads u)
 			 compile?)
 		 (if (and compile? (compile.unit-compile? u))
 		     (compile.compile-and-load u)
 		     (compile.load-source u)))
 		)))))
 
 
 (define (compile.do-delayed-loads units compile?)
   (dolist (u units)
     (compile.load-unit-recursive u compile?)))
 
 
 
 
 ;;;=====================================================================
 ;;; Extra stuff
 ;;;=====================================================================
 
 
 ;;; Reload a unit without testing to see if any of its dependencies are
 ;;; out of date.
 
 (define (reload-unit-source u)
   (let ((components  (compile.unit-components u)))
     (if (not (null? components))
 	(dolist (c components)
 	  (reload-unit-source c))
 	(compile.load-source u))))
 
 (define (reload-unit-binary u)
   (let ((components  (compile.unit-components u)))
     (if (not (null? components))
 	(dolist (c components)
 	  (reload-unit-binary c))
 	(compile.load-binary u))))
 
 
 ;;; Find a (not necessarily top-level) compilation unit with the given
 ;;; name.
 
 (define (find-unit name)
   (compile.find-unit-aux name compilation-units))
 
 (define (compile.find-unit-aux name units)
   (block find-unit-aux
     (dolist (u units '#f)
       (if (eq? name (compile.unit-name u))
 	  (return-from find-unit-aux u)
 	  (let* ((components (compile.unit-components u))
 		 (result     (compile.find-unit-aux name components)))
 	    (if result
 		(return-from find-unit-aux result)))))))
 
 
 ;;; Combine the two above:  reload a compilation unit.
 
 (define-syntax (reload name)
   `(reload-unit-source
      (or (find-unit ',name)
 	 (error "Couldn't find unit named ~s." ',name))))