319 lines
13 KiB
Scheme
319 lines
13 KiB
Scheme
(module cm mzscheme
|
|
(require (lib "modcode.ss" "syntax")
|
|
(lib "modresolve.ss" "syntax")
|
|
(lib "main-collects.ss" "setup")
|
|
(lib "file.ss"))
|
|
|
|
(provide make-compilation-manager-load/use-compiled-handler
|
|
managed-compile-zo
|
|
make-caching-managed-compile-zo
|
|
trust-existing-zos
|
|
manager-compile-notify-handler
|
|
(rename trace manager-trace-handler))
|
|
|
|
(define manager-compile-notify-handler (make-parameter void))
|
|
(define trace (make-parameter void))
|
|
(define indent (make-parameter ""))
|
|
(define trust-existing-zos (make-parameter #f))
|
|
|
|
(define (trace-printf fmt . args)
|
|
((trace) (string-append (indent) (apply format fmt args))))
|
|
|
|
(define my-max
|
|
(case-lambda
|
|
(() 0)
|
|
(x (apply max x))))
|
|
|
|
(define (get-deps code path)
|
|
(let-values ([(imports fs-imports ft-imports fl-imports) (module-compiled-imports code)])
|
|
(map path->bytes
|
|
(let ([l (map (lambda (x)
|
|
(resolve-module-path-index x path))
|
|
(append imports fs-imports ft-imports fl-imports))])
|
|
;; Filter symbols:
|
|
(let loop ([l l])
|
|
(cond
|
|
[(null? l) null]
|
|
[(symbol? (car l)) (loop (cdr l))]
|
|
[else (cons (car l) (loop (cdr l)))]))))))
|
|
|
|
(define (get-compilation-dir+name mode path)
|
|
(let-values (((base name must-be-dir?) (split-path path)))
|
|
(values
|
|
(cond
|
|
((eq? 'relative base) mode)
|
|
(else (build-path base mode)))
|
|
name)))
|
|
|
|
(define (get-compilation-path mode path)
|
|
(let-values ([(dir name) (get-compilation-dir+name mode path)])
|
|
(build-path dir name)))
|
|
|
|
(define (get-code-dir mode path)
|
|
(let-values (((base name-suffix must-be-dir?) (split-path path)))
|
|
(cond
|
|
((eq? 'relative base) mode)
|
|
(else (build-path base mode)))))
|
|
|
|
(define (try-delete-file path)
|
|
;; Attempt to delete, but give up if it
|
|
;; doesn't work:
|
|
(with-handlers ([exn:fail:filesystem? void])
|
|
(trace-printf "deleting: ~a" path)
|
|
(delete-file path)))
|
|
|
|
;; with-compile-output : path (output-port -> alpha) -> alpha
|
|
;; Open path for writing, and arranges to delete path if there's
|
|
;; an exception. Breaks are managed so that the port is reliably
|
|
;; closed and the file is reliably deleted if there's a break
|
|
(define (with-compile-output path proc)
|
|
(let ([bp (current-break-parameterization)])
|
|
(with-handlers ([void (lambda (exn)
|
|
(try-delete-file path)
|
|
(raise exn))])
|
|
(let ([out (open-output-file path 'truncate/replace)])
|
|
(dynamic-wind
|
|
void
|
|
(lambda ()
|
|
(call-with-break-parameterization
|
|
bp
|
|
(lambda ()
|
|
(proc out))))
|
|
(lambda ()
|
|
(close-output-port out)))))))
|
|
|
|
(define (write-deps code mode path external-deps)
|
|
(let ((dep-path (path-add-suffix (get-compilation-path mode path) #".dep"))
|
|
(deps (get-deps code path)))
|
|
(with-compile-output
|
|
dep-path
|
|
(lambda (op)
|
|
(write (cons (version)
|
|
(append (map path->main-collects-relative deps)
|
|
(map (lambda (x) (path->main-collects-relative (cons 'ext x)))
|
|
external-deps)))
|
|
op)
|
|
(newline op)))))
|
|
|
|
(define (touch path)
|
|
(close-output-port (open-output-file path 'append)))
|
|
|
|
(define (compilation-failure mode path zo-name date-path reason)
|
|
(with-handlers ((exn:fail:filesystem? void))
|
|
(delete-file zo-name))
|
|
(let ([fail-path (path-add-suffix (get-compilation-path mode path) #".fail")])
|
|
(with-compile-output
|
|
fail-path
|
|
(lambda (p)
|
|
(display reason p))))
|
|
(trace-printf "failure"))
|
|
|
|
(define (compile-zo mode path)
|
|
((manager-compile-notify-handler) path)
|
|
(trace-printf "compiling: ~a" path)
|
|
(parameterize ([indent (string-append " " (indent))])
|
|
(let ([zo-name (path-add-suffix (get-compilation-path mode path) #".zo")])
|
|
(cond
|
|
[(and (file-exists? zo-name) (trust-existing-zos)) (touch zo-name)]
|
|
[else
|
|
(when (file-exists? zo-name) (delete-file zo-name))
|
|
(with-handlers ([exn:get-module-code?
|
|
(lambda (ex)
|
|
(compilation-failure
|
|
mode path zo-name (exn:get-module-code-path ex)
|
|
(exn-message ex)))])
|
|
(let* ([param
|
|
;; Avoid using cm while loading cm-ctime:
|
|
(parameterize ([use-compiled-file-paths null])
|
|
(dynamic-require 'mzlib/private/cm-ctime
|
|
'current-external-file-registrar))]
|
|
[external-deps null]
|
|
[code (parameterize ([param (lambda (ext-file)
|
|
(set! external-deps
|
|
(cons (path->bytes ext-file)
|
|
external-deps)))])
|
|
(get-module-code path mode))]
|
|
[code-dir (get-code-dir mode path)])
|
|
(if (not (directory-exists? code-dir))
|
|
(make-directory* code-dir))
|
|
(with-compile-output
|
|
zo-name
|
|
(lambda (out)
|
|
(with-handlers ((exn:fail?
|
|
(lambda (ex)
|
|
(close-output-port out)
|
|
(try-delete-file zo-name)
|
|
(compilation-failure mode path zo-name #f (exn-message ex)))))
|
|
(parameterize ([current-write-relative-directory
|
|
(let-values ([(base name dir?) (split-path path)])
|
|
(if (eq? base 'relative)
|
|
(current-directory)
|
|
(path->complete-path base (current-directory))))])
|
|
(write code out)))
|
|
;; redundant, but close as early as possible:
|
|
(close-output-port out)
|
|
;; Note that we check time and write .deps before returning from with-compile-output...
|
|
(let ([ss-sec (file-or-directory-modify-seconds path)]
|
|
[zo-sec (if (file-exists? zo-name)
|
|
(file-or-directory-modify-seconds zo-name)
|
|
+inf.0)])
|
|
(when (< zo-sec ss-sec)
|
|
(error 'compile-zo
|
|
"date for newly created .zo file (~a @ ~a) is before source-file date (~a @ ~a)~a"
|
|
zo-name
|
|
(format-date (seconds->date zo-sec))
|
|
path
|
|
(format-date (seconds->date ss-sec))
|
|
(if (> ss-sec (current-seconds))
|
|
", which appears to be in the future"
|
|
""))))
|
|
(write-deps code mode path external-deps)))))])))
|
|
(trace-printf "end compile: ~a" path))
|
|
|
|
(define (format-date date)
|
|
(format "~a:~a:~a:~a:~a:~a"
|
|
(date-year date)
|
|
(date-month date)
|
|
(date-day date)
|
|
(date-hour date)
|
|
(date-minute date)
|
|
(date-second date)))
|
|
|
|
(define (append-object-suffix f)
|
|
(path-add-suffix f (system-type 'so-suffix)))
|
|
|
|
(define (get-compiled-time mode path w/fail?)
|
|
(let*-values ([(dir name) (get-compilation-dir+name mode path)])
|
|
(first-date
|
|
(lambda () (build-path dir "native" (system-library-subpath) (append-object-suffix name)))
|
|
(lambda () (build-path dir (path-add-suffix name #".zo")))
|
|
(and w/fail? (lambda () (build-path dir (path-add-suffix name #".fail")))))))
|
|
|
|
(define first-date
|
|
(case-lambda
|
|
[() -inf.0]
|
|
[(f . l)
|
|
(if f
|
|
(with-handlers ([exn:fail:filesystem?
|
|
(lambda (ex)
|
|
(apply first-date l))])
|
|
(let ([name (f)])
|
|
(file-or-directory-modify-seconds name)))
|
|
(apply first-date l))]))
|
|
|
|
(define (compile-root mode path up-to-date)
|
|
(let ([path (simplify-path (expand-path path))])
|
|
(let ((stamp (and up-to-date
|
|
(hash-table-get up-to-date path #f))))
|
|
(cond
|
|
(stamp stamp)
|
|
(else
|
|
(trace-printf "checking: ~a" path)
|
|
(let ((path-zo-time (get-compiled-time mode path #f))
|
|
(path-time
|
|
(with-handlers ((exn:fail:filesystem?
|
|
(lambda (ex)
|
|
(trace-printf "~a does not exist" path)
|
|
#f)))
|
|
(file-or-directory-modify-seconds path))))
|
|
(cond
|
|
((not path-time) path-zo-time)
|
|
(else
|
|
(cond
|
|
((> path-time path-zo-time)
|
|
(trace-printf "newer src...")
|
|
(compile-zo mode path))
|
|
(else
|
|
(let ((deps (with-handlers ((exn:fail:filesystem? (lambda (ex) (list (version)))))
|
|
(call-with-input-file (path-add-suffix (get-compilation-path mode path) #".dep")
|
|
read))))
|
|
(cond
|
|
((or (not (pair? deps))
|
|
(not (equal? (version) (car deps))))
|
|
(trace-printf "newer version...")
|
|
(compile-zo mode path))
|
|
((ormap (lambda (d)
|
|
;; str => str is a module file name (check transitive dates)
|
|
;; (cons 'ext str) => str is an non-module file (check date)
|
|
(let ([t (cond
|
|
[(bytes? d) (compile-root mode (bytes->path d) up-to-date)]
|
|
[(path? d) (compile-root mode d up-to-date)]
|
|
[(and (pair? d)
|
|
(eq? (car d) 'ext)
|
|
(or (bytes? (cdr d))
|
|
(path? (cdr d))))
|
|
(with-handlers ((exn:fail:filesystem?
|
|
(lambda (ex) +inf.0)))
|
|
(file-or-directory-modify-seconds (if (bytes? (cdr d))
|
|
(bytes->path (cdr d))
|
|
(cdr d))))]
|
|
[else +inf.0])])
|
|
(when (> t path-zo-time)
|
|
(trace-printf "newer: ~a (~a > ~a)..." d t path-zo-time))
|
|
(> t path-zo-time)))
|
|
(map main-collects-relative->path (cdr deps)))
|
|
(compile-zo mode path))))))
|
|
(let ((stamp (get-compiled-time mode path #t)))
|
|
(hash-table-put! up-to-date path stamp)
|
|
stamp)))))))))
|
|
|
|
(define (managed-compile-zo zo)
|
|
((make-caching-managed-compile-zo) zo))
|
|
|
|
(define (make-caching-managed-compile-zo)
|
|
(let ([cache (make-hash-table 'equal)])
|
|
(lambda (zo)
|
|
(parameterize ([current-load/use-compiled (make-compilation-manager-load/use-compiled-handler/table cache)])
|
|
(compile-root (car (use-compiled-file-paths)) (path->complete-path zo) cache)))))
|
|
|
|
(define (make-compilation-manager-load/use-compiled-handler)
|
|
(make-compilation-manager-load/use-compiled-handler/table (make-hash-table 'equal)))
|
|
|
|
(define (make-compilation-manager-load/use-compiled-handler/table cache)
|
|
(let ([orig-eval (current-eval)]
|
|
[orig-load (current-load)]
|
|
[orig-registry (namespace-module-registry (current-namespace))]
|
|
[default-handler (current-load/use-compiled)]
|
|
[modes (use-compiled-file-paths)])
|
|
(when (null? modes)
|
|
(raise-mismatch-error 'make-compilation-manager-...
|
|
"empty use-compiled-file-paths list: "
|
|
modes))
|
|
(letrec ([compilation-manager-load-handler
|
|
(lambda (path mod-name)
|
|
(cond
|
|
[(not mod-name)
|
|
(trace-printf "skipping: ~a mod-name ~s" path mod-name)
|
|
(default-handler path mod-name)]
|
|
[(not (member (car modes) (use-compiled-file-paths)))
|
|
(trace-printf "skipping: ~a compiled-paths ~s" path (use-compiled-file-paths))
|
|
(default-handler path mod-name)]
|
|
[(not (eq? compilation-manager-load-handler (current-load/use-compiled)))
|
|
(trace-printf "skipping: ~a current-load/use-compiled changed ~s"
|
|
path
|
|
(current-load/use-compiled))
|
|
(default-handler path mod-name)]
|
|
[(not (eq? orig-eval (current-eval)))
|
|
(trace-printf "skipping: ~a orig-eval ~s current-eval ~s"
|
|
path orig-eval
|
|
(current-eval))
|
|
(default-handler path mod-name)]
|
|
[(not (eq? orig-load (current-load)))
|
|
(trace-printf "skipping: ~a orig-load ~s current-load ~s"
|
|
path
|
|
orig-load
|
|
(current-load))
|
|
(default-handler path mod-name)]
|
|
[(not (eq? orig-registry (namespace-module-registry (current-namespace))))
|
|
(trace-printf "skipping: ~a orig-rgistry ~s current-registry ~s"
|
|
path
|
|
orig-registry
|
|
(namespace-module-registry (current-namespace)))
|
|
(default-handler path mod-name)]
|
|
[else
|
|
(trace-printf "processing: ~a" path)
|
|
(compile-root (car modes) path cache)
|
|
(trace-printf "done: ~a" path)
|
|
(default-handler path mod-name)]))])
|
|
compilation-manager-load-handler))))
|