raco exe: make generated name for embedded module deterministic

Help avoid problems with serialization by making the generation of
embedded module symbolic names deterministic and relatively
insensitive to module order. The generated name is based on a
combination of `path->module-path` and paths relative to the
main module of the executable.

Related to #2693
This commit is contained in:
Matthew Flatt 2019-06-13 07:48:20 -06:00
parent 1a7d881aba
commit 61bf75962c
3 changed files with 88 additions and 40 deletions

View File

@ -113,9 +113,7 @@ The embedded code consists of module declarations followed by
additional (arbitrary) code. When a module is embedded, every module
that it imports is also embedded. Library modules are embedded so that
they are accessible via their @racket[lib] paths in the initial
namespace except as specified in @racket[mod-list], other modules
(accessed via local paths and absolute paths) are embedded with a
generated prefix, so that they are not directly accessible.
namespace.
The @racket[#:modules] argument @racket[mod-list] designates modules
to be embedded, as described below. The @racket[#:early-literal-expressions], @racket[#:literal-files], and
@ -164,6 +162,16 @@ are available and included, the submodule is given a name by
symbol-appending the @racket[write] form of the submodule path to the
enclosing module's name.
When an embedded module is not listed in the @racket[#:modules]
argument or not given a prefix there, a symbolic name for the embedded
module is generated automatically. The names are generated in a
deterministic but unspecified way, so that they are not conveniently
accessible. The generated names may depend on the path of the first
element of @racket[mod-list]. Modules that were included via a
collection-based path remain accessible at run time through their
collection-based paths (via a module name resolver that is installed
for the embedding executable).
Modules are normally compiled before they are embedded into the target
executable; see also @racket[#:compiler] and @racket[#:src-filter]
below. When a module declares run-time paths via
@ -378,7 +386,9 @@ actual file name (e.g., @filepath{.ss}/@filepath{.rkt} conversions
have been applied as needed to refer to the existing file).
@history[#:changed "6.90.0.23" @elem{Added @racket[embed-dlls?] as an
@racket[#:aux] key.}]}
@racket[#:aux] key.}
#:changed "7.3.0.6" @elem{Changed generation of symbolic names for embedded
modules to make it deterministic.}]}
@defproc[(make-embedding-executable [dest path-string?]

View File

@ -77,9 +77,9 @@ instead of its original filesystem-based name. The module-name
resolver is configured in the embedding executable to map
collection-based module paths to the embedded symbolic name, but no
such mapping is created for filesystem paths. By default, a module's
symbolic name is generated in an unspecified but quasi-deterministic
symbolic name is generated in an unspecified but deterministic
way where the name starts with @as-index{@litchar{#%embedded:}},
except that the main module is prefixd with @litchar{#%mzc:}. The
except that the main module is prefixed with @litchar{#%mzc:}. The
relative lack of specification for module names can be be a problem
form language constructs that are sensitive to a module names, such as
serialization. To take more control over a module's symbolic name, use
@ -250,7 +250,9 @@ The @exec{raco exe} command accepts the following command-line flags:
@racketidfont{declare-preserve-for-embedding}.}
#:changed "6.90.0.23" @elem{Added @DFlag{embed-dlls}.}
#:changed "7.0.0.17" @elem{Added @DPFlag{lang}.}
#:changed "7.3.0.6" @elem{Added @DPFlag{named-lib} and @DPFlag{named-file}.}]
#:changed "7.3.0.6" @elem{Added @DPFlag{named-lib} and @DPFlag{named-file},
and changed generation of symbolic names for embedded
modules to make it deterministic.}]
@; ----------------------------------------------------------------------

View File

@ -10,6 +10,7 @@
xml/plist
setup/dirs
setup/variant
setup/collects
file/ico
racket/private/so-search
setup/cross-system
@ -318,12 +319,12 @@
;; Represent modules with lists starting with the filename, so we
;; can use assoc:
(define (make-mod normal-file-path normal-module-path
code name prefix full-name relative-mappings-box
code name full-name relative-mappings-box
runtime-paths runtime-module-syms
actual-file-path
use-source?)
(list normal-file-path normal-module-path code
name prefix full-name relative-mappings-box
name full-name relative-mappings-box
runtime-paths runtime-module-syms
actual-file-path
use-source?))
@ -332,16 +333,40 @@
(define (mod-mod-path m) (cadr m))
(define (mod-code m) (caddr m))
(define (mod-name m) (list-ref m 3))
(define (mod-prefix m) (list-ref m 4))
(define (mod-full-name m) (list-ref m 5))
(define (mod-mappings m) (unbox (list-ref m 6)))
(define (mod-runtime-paths m) (list-ref m 7))
(define (mod-runtime-module-syms m) (list-ref m 8))
(define (mod-actual-file m) (list-ref m 9))
(define (mod-use-source? m) (list-ref m 10))
(define (mod-full-name m) (list-ref m 4))
(define (mod-mappings m) (unbox (list-ref m 5)))
(define (mod-runtime-paths m) (list-ref m 6))
(define (mod-runtime-module-syms m) (list-ref m 7))
(define (mod-actual-file m) (list-ref m 8))
(define (mod-use-source? m) (list-ref m 9))
(define (generate-prefix)
(format "#%embedded:~a:" (gensym)))
(define (file-mod-name-base path)
(define-values (base name dir?) (split-path path))
(path->string (path-replace-extension name #"")))
(struct file-mod-name-state (path->relative-cache used wrt-path))
(define (make-generate-file-mod-name-state wrt-path)
(file-mod-name-state (make-hash) (make-hasheq) wrt-path))
(define (generate-file-mod-name gen-state path)
(define mp (path->module-path path
#:cache (file-mod-name-state-path->relative-cache gen-state)))
(define str
(cond
[(and mp (pair? mp) (eq? (car mp) 'lib) (null? (cddr mp)))
(cadr mp)]
[else
(define rel (find-relative-path (file-mod-name-state-wrt-path gen-state) path))
(path->string rel)]))
(define sym (string->symbol (regexp-replace #rx"[.](?:ss|rkt)$" str "")))
(define used (file-mod-name-state-used gen-state))
(let loop ([sym sym])
(cond
[(hash-ref used sym #f)
(loop (string->symbol (format "~a>" sym)))]
[else
(hash-set! used sym #t)
(format "#%embedded:~a:" sym)])))
(define (normalize filename)
(if (pair? filename)
@ -421,8 +446,8 @@
(define-struct extension (path))
;; Loads module code, using .zo if there, compiling from .scm if not
(define (get-code filename module-path ready-code use-submods codes prefixes verbose? collects-dest on-extension
compiler expand-namespace src-filter get-extra-imports working)
(define (get-code filename module-path ready-code use-submods codes file-mod-names verbose? collects-dest on-extension
compiler expand-namespace src-filter get-extra-imports working gen-state)
;; filename can have the form `(submod ,filename ,sym ...)
(let* ([a (assoc filename (unbox codes))]
;; If we didn't fine `filename` as-is, check now for
@ -472,14 +497,19 @@
[just-filename (strip-submod filename)]
[root-module-path (strip-submod module-path)]
[actual-filename just-filename] ; `set!'ed below to adjust file extension
[name (let-values ([(base name dir?) (split-path just-filename)])
(path->string (path-replace-extension name #"")))]
[prefix (let ([a (assoc just-filename prefixes)])
(if a
(cdr a)
(generate-prefix)))]
[name (file-mod-name-base just-filename)]
[file-mod-name (let ([a
;; Try path with a submodule, first, then fall back to
;; just the path part if there was a `submod` wrapper:
(or (assoc filename file-mod-names)
(and (pair? filename)
(assoc just-filename file-mod-names)))])
(if a
(cdr a)
(generate-file-mod-name gen-state just-filename)))]
[full-name (string->symbol
(format "~a~a~a" prefix name
(format "~a~a"
file-mod-name
(if (null? submod-path)
""
submod-path)))])
@ -518,7 +548,7 @@
(eprintf " using extension: ~s\n" (extension-path code)))
(set-box! codes
(cons (make-mod filename module-path code
name prefix full-name
name full-name
(box null) null null
actual-filename
#f)
@ -614,14 +644,15 @@
(define (get-one-code sub-filename sub-path ready-code)
(get-code sub-filename sub-path ready-code null
codes
prefixes
file-mod-names
verbose?
collects-dest
on-extension
compiler
expand-namespace
src-filter get-extra-imports
working))
working
gen-state))
(define (get-one-submodule-code m)
(define name (cadr (module-compiled-name m)))
(define mp `(submod "." ,name))
@ -658,7 +689,7 @@
;; Record module as copied
(set-box! codes
(cons (make-mod filename module-path #f
#f #f #f
#f #f
(box null) null null
actual-filename
use-source?)
@ -708,7 +739,7 @@
;; Record the module
(set-box! codes
(cons (make-mod filename module-path code
name prefix full-name
name full-name
mappings-box
runtime-paths
;; extract runtime-path module symbols:
@ -734,7 +765,7 @@
[else
(set-box! codes
(cons (make-mod filename module-path code
name #f #f
name #f
null null null
actual-filename
use-source?)
@ -1144,12 +1175,17 @@
[collapse-one (lambda (mp)
(collapse-module-path mp (build-path (current-directory) "dummy.rkt")))]
[collapsed-mps (map collapse-one module-paths)]
[prefix-mapping (map (lambda (f m)
(cons f (let ([p (car m)])
[gen-state (make-generate-file-mod-name-state (or (and (pair? files)
(let-values ([(base name dir) (split-path (car files))])
base))
(current-directory)))]
[file-mod-names (map (lambda (f m)
(cons f (let ([p (car m)]
[f (strip-submod f)])
(cond
[(symbol? p) (symbol->string p)]
[(eq? p #t) (generate-prefix)]
[(not p) ""]
[(symbol? p) (format "~a~a" p (file-mod-name-base f))]
[(eq? p #t) (generate-file-mod-name gen-state f)]
[(not p) (file-mod-name-base f)]
[else (error
'write-module-bundle
"bad prefix: ~e"
@ -1160,10 +1196,10 @@
;; loading imports, so the list in the right order.
[codes (box null)]
[get-code-at (lambda (f mp submods)
(get-code f mp #f submods codes prefix-mapping verbose? collects-dest
(get-code f mp #f submods codes file-mod-names verbose? collects-dest
on-extension compiler expand-namespace
src-filter get-extra-imports
(make-hash)))]
(make-hash) gen-state))]
[__
;; Load all code:
(for-each get-code-at files collapsed-mps use-submoduless)]