diff options
author | Jack Jansen <jack.jansen@cwi.nl> | 1998-06-26 14:56:00 (GMT) |
---|---|---|
committer | Jack Jansen <jack.jansen@cwi.nl> | 1998-06-26 14:56:00 (GMT) |
commit | 144fa678d28492b5544b6a31d947b3cd1a2e5ef9 (patch) | |
tree | bed5ac957f360dd4622a49d3295c3ce5a8c0e4b2 /Mac/Tools/macfreeze | |
parent | fa68b07526082bfcece1c5a17ef63e46fc7cb82b (diff) | |
download | cpython-144fa678d28492b5544b6a31d947b3cd1a2e5ef9.zip cpython-144fa678d28492b5544b6a31d947b3cd1a2e5ef9.tar.gz cpython-144fa678d28492b5544b6a31d947b3cd1a2e5ef9.tar.bz2 |
Mac version of freeze. Uses standard freeze modules where it can,
augmenting them here and there. For now, it works more-or-less similar
to unix/windows freeze, generating a config.c file, but storing
modules in PYC resources. A template project is also copied.
The hooks are in place to freeze by merging shared libraries so you
can freeze without a C compiler/linker, but this does not work yet.
Diffstat (limited to 'Mac/Tools/macfreeze')
-rw-r--r-- | Mac/Tools/macfreeze/directives.py | 39 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macfreeze.py | 72 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macfreeze.rsrc.hqx | 21 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macfreezegui.py | 158 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macgen_bin.py | 6 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macgen_info.py | 8 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macgen_rsrc.py | 35 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macgen_src.py | 109 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macgenerate.py | 8 | ||||
-rw-r--r-- | Mac/Tools/macfreeze/macmodulefinder.py | 101 |
10 files changed, 557 insertions, 0 deletions
diff --git a/Mac/Tools/macfreeze/directives.py b/Mac/Tools/macfreeze/directives.py new file mode 100644 index 0000000..e233745 --- /dev/null +++ b/Mac/Tools/macfreeze/directives.py @@ -0,0 +1,39 @@ +import re +import os + +# The regular expression for freeze directives. These are comments with the +# word macfreeze immedeately followed by a colon, followed by a directive, +# followed by argument(s) +# +# The directives supported are +# include - Include a module or file +# exclude - Exclude a module +# path - Add sys.path entries. Relative paths are relative to the source file. +# +# See the macfreeze.py main program for a real live example. +# +DIRECTIVE_RE=r'^\s*#\s*macfreeze:\s*(\S*)\s*(.*)\s*$' +REPROG=re.compile(DIRECTIVE_RE) + +def findfreezedirectives(program): + extra_modules = [] + exclude_modules = [] + extra_path = [] + progdir, filename = os.path.split(program) + fp = open(program) + for line in fp.readlines(): + match = REPROG.match(line) + if match: + directive = match.group(1) + argument = match.group(2) + if directive == 'include': + extra_modules.append(argument) + elif directive == 'exclude': + exclude_modules.append(argument) + elif directive == 'path': + argument = os.path.join(progdir, argument) + extra_path.append(argument) + else: + print '** Unknown directive', line + return extra_modules, exclude_modules, extra_path + diff --git a/Mac/Tools/macfreeze/macfreeze.py b/Mac/Tools/macfreeze/macfreeze.py new file mode 100644 index 0000000..60962ee --- /dev/null +++ b/Mac/Tools/macfreeze/macfreeze.py @@ -0,0 +1,72 @@ +"""macfreeze - Main program and GUI + +macfreeze allows you to turn Python scripts into fully self-contained +Mac applications, by including all the Python and C code needed in a single +executable. Like unix/windows freeze it can produce a config.c allowing you +to build the application with a development environment (CodeWarrior, to be +precise), but unlike the standard freeze it is also possible to create frozen +applications without a development environment, by glueing all the +shared libraries and extension modules needed together in a single +executable, using some Code Fragment Manager tricks.""" + +import macfs +import sys +import EasyDialogs +import string + +import macfreezegui +import macmodulefinder + +# +# Here are the macfreeze directives, used when freezing macfreeze itself +# (see directives.py for an explanation) +# +# macfreeze: path ::::Tools:freeze +# macfreeze: exclude win32api +# + +def main(): + if len(sys.argv) < 2: + gentype, program, output, debug = macfreezegui.dialog() + elif len(sys.argv) == 2: + gentype, program, output, debug = macfreezegui.dialog(sys.argv[1]) + else: + EasyDialog.Message( + "Please pass a single script. Additional modules can be specified with directives") + sys.exit(0) + mustwait = process(gentype, program, output, debug=debug) + if mustwait: + sys.exit(1) + +def process(gentype, program, output, modules=[], module_files=[], debug=0): + try: + module_dict = macmodulefinder.process(program, modules, module_files, debug) + except macmodulefinder.Missing, arg: + arg.sort() + print '** Missing modules:', string.join(arg, ' ') + sys.exit(1) + # + # And generate + # + if gentype == 'info': + import macgen_info + macgen_info.generate(output, module_dict) + return 1 # So the user can inspect it + elif gentype == 'source': + import macgen_src + warnings = macgen_src.generate(output, module_dict, debug) + return warnings + elif gentype == 'resource': + import macgen_rsrc + macgen_rsrc.generate(output, module_dict, debug) + warnings = macgen_rsrc.warnings(module_dict) + return warnings + elif gentype == 'applet': + import macgen_bin + macgen_bin.generate(output, module_dict, debug) + else: + raise 'unknown gentype', gentype + +if __name__ == '__main__': + main() + diff --git a/Mac/Tools/macfreeze/macfreeze.rsrc.hqx b/Mac/Tools/macfreeze/macfreeze.rsrc.hqx new file mode 100644 index 0000000..303068b --- /dev/null +++ b/Mac/Tools/macfreeze/macfreeze.rsrc.hqx @@ -0,0 +1,21 @@ +(This file must be converted with BinHex 4.0) + +:$QeKBfCbC@9kC5jbFh*M!(*cFQ058d9%!3#3"`1-AQF!N!3"!!!!!cS!!!)k!!! +!8M4lA$"F-M!dA$)`0(YF-&`b-$4F-M!dHe``A$)`0&`b$QeKBfCbC@9kC5jbFh* +MFfaKH@9bFR0bG(4iG#ia0f`!-(*cFQ058d9%!3!!J!#"!*!5XE4#FJ#3"J0ZA$- +b-&`c0cGF-&`b06"F-c)`A$-h0e``A$)e-&`c-M"F-cFhA$"F-M8`A$-b-&`c0cG +F-&`b06"F-c)`A$-h0e``A$)e-&`c-M"F-cFhA$"F-M8`A$-b-&`c0cGF-&`b06" +F-c)`A$-h0e``A$)e-&`c-M"F-cFhA$"F-M8`A$-b-!!!!"J!2!!U!@)"m3!&!*! +)!J!!!$!+!!!#'J!6!*!&$`!*!"m!9)J(8f0bDA"d1J#3"Ji!L`!H!@@)"R0MFQP +`G!#3"3d"F3!K!Ed%#8*bEhGcC5iZ,R)!N!93!!N!BJ&P"L0(C@jPFQ&dC5"bCA0 +[GA*MC5"KEQ3JFfpeFQ0P)'C[FL"$9f`!N!9S!"X!H!#"L!j2GA4`GA3JCQpXC'9 +b1J#3"@J!L`"i!@@)"R0MFQP`G!#3"@F"F3"k!Ed%#8*bEhGcC5iZ,Q%!N!@%!!N +!PJ&P"Kp(C@jPFQ&dC5"3@8-JFQ9cEh9bBf8JCQPXC5"[EQajG!#3"Cd!'`#Y!(f +)$%peG("eG#"QD@aP1J#3"Ci!L!#Y!@@)"R0MFQP`G!#3"CX"F!#[!Ed%#8*bEhG +cC5iZ,Q%!N!@j!!N!bJ&L"L9(C@jPFQ&dC5"KF("XD@0KG'P[EL"QFQpY)(0SBA* +PC#"XD@*cE!#3"G%!'`$K!(b)$%peG("eG#"QD@aP1J#3"G%!L!$K!@@)"R0MFQP +`G!#3"Fm"E`$M!Ed%#8*bEhGcC5iZ,T`!N!3""`&[!4X"[33#6dX!N!3""J!,!4S +!8!3'3f&ZBf9X!*!&+J!*!$X!ISJ04'9LG@FJ)'aPGQ9X1XJ!N!8U!)X!1J$@%!% +aG!#3"H`!#3$q!@-'%%PZCQpbE@&dD@pZ)'pZE(N!!!%!!!!$1J!!!MS!!!"5"cH +I$#UD!!!!(!"'!!&%6%p(!!!!%N4*9%`!!!!H!J#3"`FhXI3#!2rr!!!!(!FhY5! +,6@&TEL"ND@&XEfGZ5!: diff --git a/Mac/Tools/macfreeze/macfreezegui.py b/Mac/Tools/macfreeze/macfreezegui.py new file mode 100644 index 0000000..bd70c00 --- /dev/null +++ b/Mac/Tools/macfreeze/macfreezegui.py @@ -0,0 +1,158 @@ +"""macfreezegui - The GUI for macfreeze""" +import Dlg +import macfs +import EasyDialogs +import sys +import os +import string +import Res + +ID_MAINDIALOG=512 + +ITEM_SCRIPTNAME=2 +ITEM_SCRIPTBROWSE=3 +ITEM_GENSOURCE=4 +ITEM_GENSOURCE_ITEMS=(7,) +ITEM_SOURCEDIRNAME=6 +ITEM_SOURCEDIRBROWSE=7 +ITEM_GENRESOURCE=8 +ITEM_GENRESOURCE_ITEMS=(11,) +ITEM_RESOURCENAME=10 +ITEM_RESOURCEBROWSE=11 +ITEM_GENAPPLET=12 +ITEM_GENAPPLET_ITEMS=(15,) +ITEM_APPLETNAME=14 +ITEM_APPLETBROWSE=15 +ITEM_OK=16 +ITEM_CANCEL=17 +ITEM_DEBUG=19 +ITEM_GENINFO=20 + +RADIO_GROUPING={ + ITEM_GENSOURCE: ITEM_GENSOURCE_ITEMS, + ITEM_GENRESOURCE: ITEM_GENRESOURCE_ITEMS, + ITEM_GENAPPLET: ITEM_GENAPPLET_ITEMS, + ITEM_GENINFO: () +} + +def dialog(script=None): + + # Invent the various names + if not script: + fss, ok = macfs.PromptGetFile("Script?", "TEXT") + if not ok: + sys.exit(0) + script = fss.as_pathname() + basename, ext = os.path.splitext(script) + if ext: + appletname = basename + rsrcname = basename + 'modules.rsrc' + else: + appletname = script + '.applet' + rsrcname = script + 'modules.rsrc' + dirname, basebase = os.path.split(basename) + dirname = os.path.join(dirname, 'build.'+basebase) + + # Get the dialog, possibly opening the resource file (if needed) + try: + d = Dlg.GetNewDialog(ID_MAINDIALOG, -1) + except Dlg.Error: + d = None + if d == None: + try: + Res.OpenResFile('macfreeze.rsrc') + except Res.Error: + d = None + else: + d = Dlg.GetNewDialog(ID_MAINDIALOG, -1) + if d == None: + EasyDialogs.Message("Dialog resource not found or faulty") + sys.exit(1) + + # Fill the dialog + d.SetDialogDefaultItem(ITEM_OK) + d.SetDialogCancelItem(ITEM_CANCEL) + + _dialogsetfile(d, ITEM_SCRIPTNAME, script) + _dialogsetfile(d, ITEM_SOURCEDIRNAME, dirname) + _dialogsetfile(d, ITEM_RESOURCENAME, rsrcname) + _dialogsetfile(d, ITEM_APPLETNAME, appletname) + + gentype = ITEM_GENSOURCE + _dialogradiogroup(d, ITEM_GENSOURCE) + + # Interact + d.GetDialogWindow().SetWTitle("Standalone application creation options") + d.GetDialogWindow().ShowWindow() + d.DrawDialog() + while 1: + item = Dlg.ModalDialog(None) + if item == ITEM_OK: + break + elif item == ITEM_CANCEL: + sys.exit(0) + elif item in RADIO_GROUPING.keys(): + gentype = item + _dialogradiogroup(d, item) + elif item == ITEM_SCRIPTBROWSE: + fss, ok = macfs.PromptGetFile("Script?") + if ok: + script = fss.as_pathname() + _dialogsetfile(d, ITEM_SCRIPTNAME, script) + elif item == ITEM_SOURCEDIRBROWSE: + fss, ok = macfs.StandardPutFile("Output folder name", os.path.split(dirname)[1]) + if ok: + dirname = fss.as_pathname() + _dialogsetfile(d, ITEM_SOURCEDIRNAME, dirname) + elif item == ITEM_RESOURCEBROWSE: + fss, ok = macfs.StandardPutFile("Resource output file", os.path.split(rsrcname)[1]) + if ok: + rsrcname = fss.as_pathname() + _dialogsetfile(d, ITEM_RESOURCENAME, rsrcname) + elif item == ITEM_APPLETBROWSE: + fss, ok = macfs.StandardPutFile("Applet output file", os.path.split(appletname)[1]) + if ok: + appletname = fss.as_pathname() + _dialogsetfile(d, ITEM_APPLETNAME, appletname) + else: + pass + tp, h, rect = d.GetDialogItem(ITEM_DEBUG) + debug = Dlg.GetDialogItemText(h) + try: + debug = string.atoi(string.strip(debug)) + except ValueError: + EasyDialogs.Message("Illegal debug value %s, set to zero."%`debug`) + debug = 0 + if gentype == ITEM_GENSOURCE: + return 'source', script, dirname, debug + elif gentype == ITEM_GENRESOURCE: + return 'resource', script, rsrcname, debug + elif gentype == ITEM_GENAPPLET: + return 'applet', script, appletname, debug + elif gentype == ITEM_GENINFO: + return 'info', script, '', debug + raise 'Error in gentype', gentype + +def _dialogradiogroup(d, item): + for k in RADIO_GROUPING.keys(): + subitems = RADIO_GROUPING[k] + tp, h, rect = d.GetDialogItem(k) + if k == item: + h.as_Control().SetControlValue(1) + for i2 in subitems: + d.ShowDialogItem(i2) + else: + h.as_Control().SetControlValue(0) + for i2 in subitems: + d.HideDialogItem(i2) + +def _dialogsetfile(d, item, file): + if len(file) > 32: + file = '\311:' + os.path.split(file)[1] + tp, h, rect = d.GetDialogItem(item) + Dlg.SetDialogItemText(h, file) + +if __name__ == '__main__': + type, script, file, debug = dialog() + print type, script, file, 'debug=%d'%debug + sys.exit(1) diff --git a/Mac/Tools/macfreeze/macgen_bin.py b/Mac/Tools/macfreeze/macgen_bin.py new file mode 100644 index 0000000..d524e9b --- /dev/null +++ b/Mac/Tools/macfreeze/macgen_bin.py @@ -0,0 +1,6 @@ +"""macgen_bin - Generate application from shared libraries""" +import EasyDialogs + +def generate(output, module_dict): + EasyDialogs.Message('Not yet implemented') + diff --git a/Mac/Tools/macfreeze/macgen_info.py b/Mac/Tools/macfreeze/macgen_info.py new file mode 100644 index 0000000..9ec6aa0 --- /dev/null +++ b/Mac/Tools/macfreeze/macgen_info.py @@ -0,0 +1,8 @@ +"""macgen_info - Generate informational output""" + +def generate(output, module_dict): + for name in module_dict.keys(): + print 'Include %-20s\t'%name, + module = module_dict[name] + print module.gettype(), '\t', `module` + return 0 diff --git a/Mac/Tools/macfreeze/macgen_rsrc.py b/Mac/Tools/macfreeze/macgen_rsrc.py new file mode 100644 index 0000000..107e734 --- /dev/null +++ b/Mac/Tools/macfreeze/macgen_rsrc.py @@ -0,0 +1,35 @@ +"""macgen_info - Generate PYC resource file only""" +import EasyDialogs +import py_resource +import Res +import sys + +def generate(output, module_dict, debug=0, preload=1): + fsid = py_resource.create(output) + + for name, module in module_dict.items(): + if module.gettype() != 'module': + continue + location = module.__file__ + + if location[-4:] == '.pyc': + # Attempt corresponding .py + location = location[:-1] + if location[-3:] != '.py': + print '*** skipping', location + continue + + id, name = py_resource.frompyfile(location, name, preload=preload) + if debug > 0: + print 'PYC resource %5d\t%s\t%s'%(id, name, location) + + Res.CloseResFile(fsid) + +def warnings(module_dict): + problems = 0 + for name, module in module_dict.items(): + if module.gettype() not in ('builtin', 'module'): + problems = problems + 1 + print 'Warning: %s not included: %s %s'%(name, module.gettype(), module) + return problems + diff --git a/Mac/Tools/macfreeze/macgen_src.py b/Mac/Tools/macfreeze/macgen_src.py new file mode 100644 index 0000000..ad0739e --- /dev/null +++ b/Mac/Tools/macfreeze/macgen_src.py @@ -0,0 +1,109 @@ +"""macgen_info - Generate CodeWarrior project, config source, resource file""" +import EasyDialogs +import os +import sys +import macfs +import MacOS +import macostools +import macgen_rsrc +# Note: this depends on being frozen, or on sys.path already being +# modified by macmodulefinder. +import makeconfig + +TEMPLATEDIR=os.path.join(sys.prefix, ':Mac:mwerks:projects:build.macfreeze') +PROJECT_TEMPLATE=os.path.join(TEMPLATEDIR, ':frozen.prj') +CONFIG_TEMPLATE=os.path.join(TEMPLATEDIR, ':templatefrozenconfig.c') +BUNDLE_TEMPLATE=os.path.join(TEMPLATEDIR, ':frozenbundle.rsrc') + +def generate(output, module_dict, debug=0): + problems = 0 + output_created=0 + if not os.path.exists(output): + print 'Creating project folder', output + os.mkdir(output) + output_created = 1 + # Resolve aliases, if needed + try: + fss, dummy1, dummy2 = macfs.ResolveAliasFile(output) + except macfs.error: + pass + else: + newname = fss.as_pathname() + if newname != output: + if debug: + print 'Alias', output + print 'Resolved to', newname + output = newname + # Construct the filenames + dummy, outfile = os.path.split(output) + build, ext = os.path.splitext(outfile) + if build == 'build' and ext[0] == '.': + # This is probably a good name for the project + projname = ext[1:] + else: + projname = 'frozenapplet.prj' + config_name = os.path.join(output, ':macfrozenconfig.c') + project_name = os.path.join(output, ':' + projname + '.prj') + resource_name = os.path.join(output, ':frozenmodules.rsrc') + bundle_name = os.path.join(output, ':frozenbundle.rsrc') + + # Fill the output folder, if needed. + if output_created: + # Create the project, if needed + if not os.path.exists(project_name): + print 'Creating project', project_name + if not os.path.exists(PROJECT_TEMPLATE): + print '** No template CodeWarrior project found at', PROJECT_TEMPLATE + print ' To generate standalone Python applications from source you need' + print ' a full source distribution. Check http://www.cwi.nl/~jack/macpython.html' + print ' for details.' + problems = 1 + else: + macostools.copy(PROJECT_TEMPLATE, project_name) + print 'A template CodeWarrior project has been copied to', project_name + print 'It is up to you to make the following changes:' + print '- Change the output file name' + print '- Change the search path, unless the folder is in the python home' + print '- Add sourcefiles/libraries for any extension modules used' + print '- Remove unused sources, to speed up the build process' + print '- Remove unused resource files (like tcl/tk) for a smaller binary' + problems = 1 + macostools.copy(BUNDLE_TEMPLATE, bundle_name) + print 'A template bundle file has also been copied to', bundle_name + print 'You may want to adapt signature, size resource, etc' + + + # Create the resource file + macgen_rsrc.generate(resource_name, module_dict, debug=debug) + + # Create the config.c file + if not os.path.exists(CONFIG_TEMPLATE): + print '** No template config.c found at', PROJECT_TEMPLATE + print ' To generate standalone Python applications from source you need' + print ' a full source distribution. Check http://www.cwi.nl/~jack/macpython.html' + print ' for details.' + problems = 1 + else: + # Find elegible modules (builtins and dynamically loaded modules) + c_modules = [] + for module in module_dict.keys(): + if module_dict[module].gettype() in ('builtin', 'dynamic'): + c_modules.append(module) + ifp = open(CONFIG_TEMPLATE) + ofp = open(config_name, 'w') + makeconfig.makeconfig(ifp, ofp, c_modules) + ifp.close() + ofp.close() + MacOS.SetCreatorAndType(config_name, 'CWIE', 'TEXT') + + if warnings(module_dict): + problems = 1 + return problems + +def warnings(module_dict): + problems = 0 + for name, module in module_dict.items(): + if module.gettype() not in ('builtin', 'module', 'dynamic'): + problems = problems + 1 + print 'Warning: %s not included: %s %s'%(name, module.gettype(), module) + return problems diff --git a/Mac/Tools/macfreeze/macgenerate.py b/Mac/Tools/macfreeze/macgenerate.py new file mode 100644 index 0000000..6c60605 --- /dev/null +++ b/Mac/Tools/macfreeze/macgenerate.py @@ -0,0 +1,8 @@ +"""macgenerate - Generate the out for macfreeze""" + +def generate(program, module_dict): + for name in module_dict.keys(): + print 'Include %-20s\t'%name, + module = module_dict[name] + print module.gettype(), '\t', `module` + return 0 diff --git a/Mac/Tools/macfreeze/macmodulefinder.py b/Mac/Tools/macfreeze/macmodulefinder.py new file mode 100644 index 0000000..d87ca9b --- /dev/null +++ b/Mac/Tools/macfreeze/macmodulefinder.py @@ -0,0 +1,101 @@ +"""macmodulefinder - Find modules used in a script. Only slightly +mac-specific, really.""" + +import sys +import os + +import directives + +try: + # This will work if we are frozen ourselves + import modulefinder +except ImportError: + # And this will work otherwise + _FREEZEDIR=os.path.join(sys.prefix, ':Tools:freeze') + sys.path.insert(0, _FREEZEDIR) + import modulefinder + +# +# Modules that must be included, and modules that need not be included +# (but are if they are found) +# +MAC_INCLUDE_MODULES=['site', 'exceptions'] +MAC_MAYMISS_MODULES=['posix', 'os2', 'nt', 'dos', 'dospath', 'nturl2path', 'pwd', 'sitecustomize'] + +# An exception: +Missing="macmodulefinder.Missing" + +class Module(modulefinder.Module): + + def gettype(self): + """Return type of module""" + if self.__path__: + return 'package' + if self.__code__: + return 'module' + if self.__file__: + return 'dynamic' + return 'builtin' + +class ModuleFinder(modulefinder.ModuleFinder): + + def add_module(self, fqname): + if self.modules.has_key(fqname): + return self.modules[fqname] + self.modules[fqname] = m = Module(fqname) + return m + +def process(program, modules=[], module_files = [], debug=0): + error = [] + # + # Add the standard modules needed for startup + # + modules = modules + MAC_INCLUDE_MODULES + # + # search the main source for directives + # + extra_modules, exclude_modules, extra_path = \ + directives.findfreezedirectives(program) + for m in extra_modules: + if os.sep in m: + # It is a file + module_files.append(m) + else: + modules.append(m) + path = extra_path + sys.path[:] + # + # Create the module finder and let it do its work + # + modfinder = ModuleFinder(path, + excludes=exclude_modules, debug=debug) + for m in modules: + modfinder.import_hook(m) + for m in module_files: + modfinder.load_file(m) + modfinder.run_script(program) + module_dict = modfinder.modules + # + # Tell the user about missing modules + # + maymiss = exclude_modules + MAC_MAYMISS_MODULES + for m in modfinder.badmodules.keys(): + if not m in maymiss: + if debug > 0: + print 'Missing', m + error.append(m) + # + # Warn the user about unused builtins + # + for m in sys.builtin_module_names: + if m in ('__main__', '__builtin__'): + pass + elif not module_dict.has_key(m): + if debug > 0: + print 'Unused', m + elif module_dict[m].gettype() != 'builtin': + # XXXX Can this happen? + if debug > 0: + print 'Conflict', m + if error: + raise Missing, error + return module_dict |