Double instantiation of modules from namespace-require in executable

After compiling my Racket application to an executable with raco, I'm finding that namespace-require re-instantiates several modules. Consequently, the namespace I'm trying to load into has its contents split across two different namespace bindings. Is there some setting I can pass to raco to ensure this doesn't happen? The application where this occurs is here:

namespace-require is used only in lang/namespace-requires.rkt.

I'm compiling with the command
raco exe --cs -o shen ++lib racket/lang/reader repl.rkt

Notably this behavior does not result if I load repl.rkt into the Racket REPL and call shen-repl from there.

I posted this in Discord first, but I'll answer here too for the benefit of the forum. I've edited out some other potential approaches I considered for fixing the bug, since define-runtime-module-path works very well here.

I debugged basically just by adding a bunch more " instantiated" messages. I'm just gonna share what I've found:

  1. The module lang/interposition-points.rkt requires lang/macros.rkt, which requires lang/packages.rkt, which displays the "packages.rkt instantiated" message that you're seeing twice from the executable.
  2. The executable is based on repl.rkt.
  3. The module repl.rkt requires lang/interposition-points.rkt.
  4. The module repl.rkt requires lang/namespace-requires.rkt, which dynamically requires lang/interposition-points.rkt.

Thus the program loads two different versions of lang/interposition-points.rkt and all the modules it requires: It loads the version of lang/interposition-points.rkt that was bundled into the executable file thanks to (2) and (3), and it also loads the version of lang/interposition-points.rkt relative to the working directory thanks to (2) and (4). Thanks to (1), each of these versions gives you its own "packages.rkt instantiated" message.

Other things that can be observed about this situation, which might indicate good things to check during future debugging:

  • If the executable is moved into another directory and run from there, the dynamic load of lang/interposition-points.rkt fails, causing the program to exit with an error message.
  • If the executable is built and then the "packages.rkt instantiated" message in lang/packages.rkt is modified to say "packages.rkt instantiated from another file", then the executable displays both "packages.rkt instantiated" and "packages.rkt instantiated from another file".

As for what to do about it, you could change the dynamic namespace-require calls so they refer to the modules inside the executable file rather than the modules in the current directory. The raco exe docs say "When a module is embedded in an executable, it gets a symbolic name 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 deterministic way[...]". So the module you need to require has a difficult-to-predict name.

It looks like define-runtime-module-path is perfect for this, using the generated name when it's embedded within the executable but still using the filesystem path when run in other ways:

(define-runtime-module-path module-path-to-interposition-points "interposition-points.rkt")
(define-runtime-module-path module-path-to-system-function-exports "system-function-exports.rkt")
(define-runtime-module-path module-path-to-expander "expander.rkt")

(namespace-require `(rename ,module-path-to-interposition-points #%app app) kl-namespace)

Great, thank you! I did read that paragraph of the raco exe documentation but didn't know how to relate it to define-runtime-module-path.

1 Like