]> granicus.if.org Git - python/commitdiff
Mac version of freeze. Uses standard freeze modules where it can,
authorJack Jansen <jack.jansen@cwi.nl>
Fri, 26 Jun 1998 14:56:00 +0000 (14:56 +0000)
committerJack Jansen <jack.jansen@cwi.nl>
Fri, 26 Jun 1998 14:56:00 +0000 (14:56 +0000)
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.

Mac/Tools/macfreeze/directives.py [new file with mode: 0644]
Mac/Tools/macfreeze/macfreeze.py [new file with mode: 0644]
Mac/Tools/macfreeze/macfreeze.rsrc.hqx [new file with mode: 0644]
Mac/Tools/macfreeze/macfreezegui.py [new file with mode: 0644]
Mac/Tools/macfreeze/macgen_bin.py [new file with mode: 0644]
Mac/Tools/macfreeze/macgen_info.py [new file with mode: 0644]
Mac/Tools/macfreeze/macgen_rsrc.py [new file with mode: 0644]
Mac/Tools/macfreeze/macgen_src.py [new file with mode: 0644]
Mac/Tools/macfreeze/macgenerate.py [new file with mode: 0644]
Mac/Tools/macfreeze/macmodulefinder.py [new file with mode: 0644]

diff --git a/Mac/Tools/macfreeze/directives.py b/Mac/Tools/macfreeze/directives.py
new file mode 100644 (file)
index 0000000..e233745
--- /dev/null
@@ -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 (file)
index 0000000..60962ee
--- /dev/null
@@ -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 (file)
index 0000000..303068b
--- /dev/null
@@ -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 (file)
index 0000000..bd70c00
--- /dev/null
@@ -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 (file)
index 0000000..d524e9b
--- /dev/null
@@ -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 (file)
index 0000000..9ec6aa0
--- /dev/null
@@ -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 (file)
index 0000000..107e734
--- /dev/null
@@ -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 (file)
index 0000000..ad0739e
--- /dev/null
@@ -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 (file)
index 0000000..6c60605
--- /dev/null
@@ -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 (file)
index 0000000..d87ca9b
--- /dev/null
@@ -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