summaryrefslogtreecommitdiff
path: root/support/compile.scm
blob: 77e222fb9c6cbe05503f0085d5032f780c8c453d (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
;;; 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))))