add support for indirect CM dependencies; use in lazy-require

If module M in package P imports module N from package Q,
and if N has a `lazy-require` for a module in R that is
triggered during the compilation of M, then P doesn't really
depend on R; P depends on Q, and Q depends on R, and P
shoudn't necessarily know anything about Q. At the same time,
a change to the file in R means that M must be recompiled.
So, continue to track the compilation dependency, but mark
it as "indirect" so that the package-dependency checker can
ignore the dependency.
This commit is contained in:
Matthew Flatt 2015-01-08 09:57:00 -07:00
parent fe9a04d1db
commit 95e85ec5bd
6 changed files with 71 additions and 36 deletions

View File

@ -550,25 +550,36 @@ field is a @racket[compile-event] as document in
@defmodule[compiler/cm-accomplice]
@defproc[(register-external-file [file (and path? complete-path?)]) void?]{
@defproc[(register-external-file [file (and path? complete-path?)]
[#:indirect? indirect? any/c #f])
void?]{
Logs a message (see @racket[log-message]) at level @racket['info] to
a logger named @racket['cm-accomplice]. The
message data is a @racketidfont{file-dependency} prefab structure type
with two fields; the first field's value is @racket[file] and the second
field's value is @racket[#f] (to indicate a non-module dependency).
Logs a message (see @racket[log-message]) at level @racket['info] to a
logger named @racket['cm-accomplice]. The message data is a
@racketidfont{file-dependency} prefab structure type with two fields;
the first field's value is @racket[file] and the second field's value
is @racket[#f] (to indicate a non-module dependency). If the
@racket[indirect?] argument is true, the data is more specifically an
instance of a @racketidfont{file-dependency/indirect} prefab structure
type that is a subtype of @racketidfont{file-dependency} with no new
fields.
A compilation manager implemented by @racketmodname[compiler/cm] looks
for such messages to register an external dependency. The compilation
manager records (in a @filepath{.dep} file) the path as contributing
to the implementation of the module currently being
for such messages to register an external dependency. In response, the
compilation manager records (in a @filepath{.dep} file) the path as
contributing to the implementation of the module currently being
compiled. Afterward, if the registered file is modified, the
compilation manager will know to recompile the module.
compilation manager will know to recompile the module. An ``indirect''
dependency has no effect on recompilation, but it can signal to other
tools, such as a package-dependency checker, that the dependency is
indirect (and should not imply a direct package dependency).
The @racket[include] macro, for example, calls this procedure with the
path of an included file as it expands an @racket[include] form.}
@defproc[(register-external-module [file (and path? complete-path?)]) void?]{
@defproc[(register-external-module [file (and path? complete-path?)]
[#:indirect? indirect? any/c #f])
void?]{
Like @racket[register-external-file], but logs a message with a
@racketidfont{file-dependency} prefab structure type whose second

View File

@ -3005,10 +3005,10 @@ submodule). Introduced submodules have the names
@racket[lazy-require-]@racket[_n]@racketidfont{-}@racket[_m], where
@racket[_n] is a phase-level number and @racket[_m] is a number.
When the use of a lazily-required function triggers module loading,
@racket[register-external-module] declares a potential compilation
dependency (in case the function is used in the process of compiling a
module).
When the use of a lazily-required function triggers module loading, it
also triggers a use of @racket[register-external-module] to declare an
indirect compilation dependency (in case the function is used in the
process of compiling a module).
@examples[#:eval lazy-require-eval
(lazy-require

View File

@ -3,16 +3,18 @@
(provide register-external-file
register-external-module)
(define (register-external-file f)
(register-external 'register-external-file f #f))
(define (register-external-module f)
(register-external 'register-external-module f #t))
(define (register-external-file f #:indirect? [indirect? #f])
(register-external 'register-external-file f #f indirect?))
(define (register-external-module f #:indirect? [indirect? #f])
(register-external 'register-external-module f #t indirect?))
(define (register-external who f module?)
(define (register-external who f module? indirect?)
(unless (and (path? f) (complete-path? f))
(raise-type-error who "complete path" f))
(log-message (current-logger)
'info
'cm-accomplice
(format "file dependency: ~s" f)
`#s(file-dependency ,f ,module?)))
(if indirect?
`#s((file-dependency/indirect file-dependency 2) ,f ,module?)
`#s(file-dependency ,f ,module?))))

View File

@ -231,9 +231,13 @@
(define (get-dep-sha1s deps up-to-date collection-cache read-src-syntax mode roots must-exist? seen)
(let ([l (for/fold ([l null]) ([dep (in-list deps)])
(and l
;; (cons 'indirect dep) => indirect dependency (for pkg-dep checking)
;; (cons 'ext rel-path) => a non-module file, check source
;; rel-path => a module file name, check cache
(let* ([ext? (and (pair? dep) (eq? 'ext (car dep)))]
(let* ([dep (if (and (pair? dep) (eq? 'indirect (car dep)))
(cdr dep)
dep)]
[ext? (and (pair? dep) (eq? 'ext (car dep)))]
[p (collects-relative*->path (if ext? (cdr dep) dep) collection-cache)])
(cond
[ext? (let ([v (get-source-sha1 p)])
@ -273,19 +277,26 @@
external-module-deps ; can create cycles if misused!
reader-deps))]
[external-deps (remove-duplicates external-deps)])
(define (path*->collects-relative/maybe-indirect dep)
(if (and (pair? dep) (eq? 'indirect (car dep)))
(cons 'indirect (path*->collects-relative (cdr dep)))
(path*->collects-relative dep)))
(with-compile-output dep-path
(lambda (op tmp-path)
(let ([deps (append
(map path*->collects-relative deps)
(map path*->collects-relative/maybe-indirect deps)
(map (lambda (x)
(cons 'ext (path*->collects-relative x)))
(define d (path*->collects-relative/maybe-indirect x))
(if (and (pair? d) (eq? 'indirect d))
(cons 'indirect (cons 'ext (cdr d)))
(cons 'ext d)))
external-deps))])
(write (list* (version)
(cons (or src-sha1 (get-source-sha1 path))
(get-dep-sha1s deps up-to-date collection-cache read-src-syntax mode roots #t #hash()))
deps)
op)
(newline op))))))
(write (list* (version)
(cons (or src-sha1 (get-source-sha1 path))
(get-dep-sha1s deps up-to-date collection-cache read-src-syntax mode roots #t #hash()))
deps)
op)
(newline op))))))
(define (format-time sec)
(let ([d (seconds->date sec)])
@ -311,6 +322,7 @@
(define-struct ext-reader-guard (proc top)
#:property prop:procedure (struct-field-index proc))
(define-struct file-dependency (path module?) #:prefab)
(define-struct (file-dependency/indirect file-dependency) () #:prefab)
(define (compile-zo* mode roots path src-sha1 read-src-syntax zo-name up-to-date collection-cache)
;; The `path' argument has been converted to .rkt or .ss form,
@ -322,10 +334,14 @@
(define reader-deps null)
(define deps-sema (make-semaphore 1))
(define done-key (gensym))
(define (external-dep! p module?)
(define (external-dep! p module? indirect?)
(define bstr (path->bytes p))
(define dep (if indirect?
(cons 'indirect bstr)
bstr))
(if module?
(set! external-module-deps (cons (path->bytes p) external-module-deps))
(set! external-deps (cons (path->bytes p) external-deps))))
(set! external-module-deps (cons dep external-module-deps))
(set! external-deps (cons dep external-deps))))
(define (reader-dep! p)
(call-with-semaphore
deps-sema
@ -386,7 +402,8 @@
(file-dependency? (vector-ref l 2))
(path? (file-dependency-path (vector-ref l 2))))
(external-dep! (file-dependency-path (vector-ref l 2))
(file-dependency-module? (vector-ref l 2))))
(file-dependency-module? (vector-ref l 2))
(file-dependency/indirect? (vector-ref l 2))))
(loop))))
;; Write the code and dependencies:
@ -627,9 +644,13 @@
;; If `sha1-only?', then `maybe-compile-zo' returns a #f or thunk:
(maybe-compile-zo sha1-only? deps mode roots path orig-path read-src-syntax up-to-date collection-cache new-seen)]
[(ormap
(lambda (p)
(lambda (raw-p)
;; (cons 'indirect dep) => indirect dependency (for pkg-dep checking)
;; (cons 'ext rel-path) => a non-module file (check date)
;; rel-path => a module file name (check transitive dates)
(define p (if (and (pair? raw-p) (eq? 'indirect (car raw-p)))
(cdr raw-p)
raw-p))
(define ext? (and (pair? p) (eq? 'ext (car p))))
(define d (collects-relative*->path (if ext? (cdr p) p) collection-cache))
(define t

View File

@ -110,4 +110,4 @@
modpath
(variable-reference->resolved-module-path vr))))])
(when (path? path)
(register-external-module path))))
(register-external-module path #:indirect? #t))))

View File

@ -489,6 +489,7 @@
;; Treat everything in ".dep" as 'build mode...
(define deps (cddr (call-with-input-file* (build-path dir f) read)))
(for ([dep (in-list deps)])
;; Note: indirect dependencies (which start with 'indirect) are ignored
(when (and (pair? dep)
(eq? 'collects (car dep)))
(define path-strs (map bytes->string/utf-8 (cdr dep)))