From 78fc3634cbfd65a6be8abfd1b7fc7cbd0ccbfb39 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 20 Mar 1998 17:37:24 +0000 Subject: Big changes by Mark Hammond to support freezing with DLLs on Windows. --- Tools/freeze/freeze.py | 98 ++++++++++++++++++++++++++++------- Tools/freeze/makefreeze.py | 7 ++- Tools/freeze/modulefinder.py | 32 +++++++++++- Tools/freeze/winmakemakefile.py | 111 +++++++++++++++++++++++++++++++--------- 4 files changed, 201 insertions(+), 47 deletions(-) diff --git a/Tools/freeze/freeze.py b/Tools/freeze/freeze.py index 3de6bdb..ce0a454 100755 --- a/Tools/freeze/freeze.py +++ b/Tools/freeze/freeze.py @@ -28,6 +28,8 @@ Options: -m: Additional arguments are module names instead of filenames. +-l file: Pass the file to the linker (windows only) + -d: Debugging mode for the module finder. -q: Make the module finder totally quiet. @@ -38,8 +40,11 @@ Options: (For debugging only -- on a win32 platform, win32 behaviour is automatic.) --s subsystem: Specify the subsystem; 'windows' or 'console' (default). - (For Windows only.) +-x module Exclude the specified module. + +-s subsystem: Specify the subsystem (For Windows only.); + 'console' (default), 'windows', 'service' or 'com_dll' + Arguments: @@ -87,12 +92,17 @@ def main(): prefix = None # settable with -p option exec_prefix = None # settable with -P option extensions = [] + exclude = [] # settable with -x option + addn_link = [] # settable with -l, but only honored under Windows. path = sys.path[:] modargs = 0 debug = 1 odir = '' win = sys.platform[:3] == 'win' + # default the exclude list for each platform +# if win: exclude = exclude + ['dos', 'dospath', 'mac', 'macpath', 'MACFS', 'posix', 'os2'] + # modules that are imported by the Python runtime implicits = ["site", "exceptions"] @@ -105,7 +115,7 @@ def main(): # parse command line try: - opts, args = getopt.getopt(sys.argv[1:], 'de:hmo:p:P:qs:w') + opts, args = getopt.getopt(sys.argv[1:], 'de:hmo:p:P:qs:wx:l:') except getopt.error, msg: usage('getopt error: ' + str(msg)) @@ -134,6 +144,10 @@ def main(): if not win: usage("-s subsystem option only on Windows") subsystem = a + if o == '-x': + exclude.append(a) + if o == '-l': + addn_link.append(a) # default prefix and exec_prefix if not exec_prefix: @@ -156,6 +170,7 @@ def main(): config_c_in = os.path.join(prefix, 'Modules', 'config.c.in') frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c') makefile_in = os.path.join(exec_prefix, 'Modules', 'Makefile') + if win: frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c') else: binlib = os.path.join(exec_prefix, 'lib', 'python%s' % version, 'config') @@ -198,12 +213,15 @@ def main(): for arg in args: if arg == '-m': break + # if user specified -m on the command line before _any_ + # file names, then nothing should be checked (as the + # very first file should be a module name) + if modargs: + break if not os.path.exists(arg): usage('argument %s not found' % arg) if not os.path.isfile(arg): usage('%s: not a plain file' % arg) - if modargs: - break # process non-option arguments scriptfile = args[0] @@ -234,12 +252,32 @@ def main(): target = os.path.join(odir, target) makefile = os.path.join(odir, makefile) + # Handle special entry point requirements + # (on Windows, some frozen programs do not use __main__, but + # import the module directly. Eg, DLLs, Services, etc + custom_entry_point = None # Currently only used on Windows + python_entry_is_main = 1 # Is the entry point called __main__? + # handle -s option on Windows + if win: + import winmakemakefile + try: + custom_entry_point, python_entry_is_main = winmakemakefile. get_custom_entry_point(subsystem) + except ValueError, why: + usage(why) + + # Actual work starts here... # collect all modules of the program dir = os.path.dirname(scriptfile) path[0] = dir - mf = modulefinder.ModuleFinder(path, debug) + mf = modulefinder.ModuleFinder(path, debug, exclude) + + if win and subsystem=='service': + # If a Windows service, then add the "built-in" module. + mod = mf.add_module("servicemanager") + mod.__file__="dummy.pyd" # really built-in to the resulting EXE + for mod in implicits: mf.import_hook(mod) for mod in modules: @@ -253,7 +291,16 @@ def main(): mf.import_hook(mod) else: mf.load_file(mod) - mf.run_script(scriptfile) + + # Add the main script as either __main__, or the actual module name. + if python_entry_is_main: + mf.run_script(scriptfile) + else: + if modargs: + mf.import_hook(scriptfile) + else: + mf.load_file(scriptfile) + if debug > 0: mf.report() print @@ -267,10 +314,7 @@ def main(): backup = None outfp = open(frozen_c, 'w') try: - makefreeze.makefreeze(outfp, dict, debug) - if win and subsystem == 'windows': - import winmakemakefile - outfp.write(winmakemakefile.WINMAINTEMPLATE) + makefreeze.makefreeze(outfp, dict, debug, custom_entry_point) finally: outfp.close() if backup: @@ -294,12 +338,29 @@ def main(): # search for unknown modules in extensions directories (not on Windows) addfiles = [] - if unknown and not win: - addfiles, addmods = \ - checkextensions.checkextensions(unknown, extensions) - for mod in addmods: - unknown.remove(mod) - builtins = builtins + addmods + addmoddefns = [] # Windows list of modules. + if unknown: + if not win: + addfiles, addmods = \ + checkextensions.checkextensions(unknown, extensions) + for mod in addmods: + unknown.remove(mod) + builtins = builtins + addmods + else: + # Do the windows thang... + import checkextensions_win32 + # Get a list of CExtension instances, each describing a module + # (including its source files) + addmoddefns = checkextensions_win32.checkextensions(unknown, extensions) + maindefn = checkextensions_win32.CExtension( '__main__', + [frozenmain_c, os.path.basename(frozen_c),frozendllmain_c]) + + for mod in addmoddefns: + unknown.remove(mod.name) + builtins.append(mod.name) + + addmoddefns.append( maindefn ) + # report unknown modules if unknown: @@ -314,8 +375,7 @@ def main(): try: winmakemakefile.makemakefile(outfp, locals(), - [frozenmain_c, - os.path.basename(frozen_c)], + addmoddefns, os.path.basename(target)) finally: outfp.close() diff --git a/Tools/freeze/makefreeze.py b/Tools/freeze/makefreeze.py index 88265b0..25c52bc 100644 --- a/Tools/freeze/makefreeze.py +++ b/Tools/freeze/makefreeze.py @@ -12,7 +12,9 @@ static struct _frozen _PyImport_FrozenModules[] = { trailer = """\ {0, 0, 0} /* sentinel */ }; +""" +default_entry_point = """ int main(argc, argv) int argc; @@ -24,7 +26,8 @@ main(argc, argv) """ -def makefreeze(outfp, dict, debug=0): +def makefreeze(outfp, dict, debug=0, entry_point = None): + if entry_point is None: entry_point = default_entry_point done = [] mods = dict.keys() mods.sort() @@ -47,6 +50,8 @@ def makefreeze(outfp, dict, debug=0): for mod, mangled, size in done: outfp.write('\t{"%s", M_%s, %d},\n' % (mod, mangled, size)) outfp.write(trailer) + outfp.write(entry_point) + # Write a C initializer for a module containing the frozen python code. diff --git a/Tools/freeze/modulefinder.py b/Tools/freeze/modulefinder.py index 3aa6941..408c2ba 100644 --- a/Tools/freeze/modulefinder.py +++ b/Tools/freeze/modulefinder.py @@ -8,6 +8,16 @@ import re import string import sys +if sys.platform=="win32": + # On Windows, we can locate modules in the registry with + # the help of the win32api package. + try: + import win32api + except ImportError: + print "The win32api module is not available - modules listed" + print "in the registry will not be found." + win32api = None + IMPORT_NAME = dis.opname.index('IMPORT_NAME') IMPORT_FROM = dis.opname.index('IMPORT_FROM') @@ -33,7 +43,7 @@ class Module: class ModuleFinder: - def __init__(self, path=None, debug=0): + def __init__(self, path=None, debug=0, excludes = []): if path is None: path = sys.path self.path = path @@ -41,6 +51,7 @@ class ModuleFinder: self.badmodules = {} self.debug = debug self.indent = 0 + self.excludes = excludes def msg(self, level, str, *args): if level <= self.debug: @@ -219,7 +230,7 @@ class ModuleFinder: self.msgout(2, "load_module ->", m) return m if type == imp.PY_SOURCE: - co = compile(fp.read(), pathname, 'exec') + co = compile(fp.read()+'\n', pathname, 'exec') elif type == imp.PY_COMPILED: if fp.read(4) != imp.get_magic(): self.msgout(2, "raise ImportError: Bad magic number", pathname) @@ -289,9 +300,26 @@ class ModuleFinder: return m def find_module(self, name, path): + if name in self.excludes: + self.msgout(3, "find_module -> Excluded") + raise ImportError, name + if path is None: if name in sys.builtin_module_names: return (None, None, ("", "", imp.C_BUILTIN)) + + # Emulate the Registered Module support on Windows. + if sys.platform=="win32" and win32api is not None: + HKEY_LOCAL_MACHINE = 0x80000002 + try: + pathname = win32api.RegQueryValue(HKEY_LOCAL_MACHINE, "Software\\Python\\PythonCore\\%s\\Modules\\%s" % (sys.winver, name)) + fp = open(pathname, "rb") + # XXX - To do - remove the hard code of C_EXTENSION. + stuff = "", "rb", imp.C_EXTENSION + return fp, pathname, stuff + except win32api.error: + pass + path = self.path return imp.find_module(name, path) diff --git a/Tools/freeze/winmakemakefile.py b/Tools/freeze/winmakemakefile.py index dde241b..2c13ad3 100644 --- a/Tools/freeze/winmakemakefile.py +++ b/Tools/freeze/winmakemakefile.py @@ -1,5 +1,6 @@ import sys, os, string +# Template used then the program is a GUI program WINMAINTEMPLATE = """ #include @@ -10,10 +11,36 @@ int WINAPI WinMain( int nCmdShow // show state of window ) { - return main(__argc, __argv); + PyImport_FrozenModules = _PyImport_FrozenModules; + return Py_FrozenMain(__argc, __argv); } """ +SERVICETEMPLATE = """ +extern int PythonService_main(int, char **); + +int main( int argc, char **argv) +{ + PyImport_FrozenModules = _PyImport_FrozenModules; + return PythonService_main(argc, argv); +} +""" + +subsystem_details = { + # -s flag : (C entry point template), (is it __main__?), (is it a DLL?) + 'console' : (None, 1, 0), + 'windows' : (WINMAINTEMPLATE, 1, 0), + 'service' : (SERVICETEMPLATE, 0, 0), + 'com_dll' : ("", 0, 1), +} + +def get_custom_entry_point(subsystem): + try: + return subsystem_details[subsystem][:2] + except KeyError: + raise ValueError, "The subsystem %s is not known" % subsystem + + def makemakefile(outfp, vars, files, target): save = sys.stdout try: @@ -22,7 +49,7 @@ def makemakefile(outfp, vars, files, target): finally: sys.stdout = save -def realwork(vars, files, target): +def realwork(vars, moddefns, target): print "# Makefile for Windows (NT or 95) generated by freeze.py script" print print 'target = %s' % target @@ -30,35 +57,69 @@ def realwork(vars, files, target): # XXX The following line is fishy and may need manual fixing print 'pythonlib = "%s"' % (vars['exec_prefix'] + "/pcbuild/release/python15.lib") - print "subsystem =", vars['subsystem'] + + # We only ever write one "entry point" symbol - either + # "main" or "WinMain". Therefore, there is no need to + # pass a subsystem switch to the linker as it works it + # out all by itself. However, the subsystem _does_ determine + # the file extension and additional linker flags. + target_link_flags = "" + target_ext = ".exe" + if subsystem_details[vars['subsystem']][2]: + target_link_flags = "-dll" + target_ext = ".dll" + + print "cdl = /MD" # XXX - Should this come from vars? User may have specific requirements... print - print "all: $(target).exe" + print "all: $(target)%s" % (target_ext) print objects = [] - for file in files: - base = os.path.basename(file) - base, ext = os.path.splitext(base) - objects.append(base + ".obj") - print '%s.obj: "%s"' % (base, file) - print "\t$(CC) -c $(cdl)", - print "-I$(pythonhome)/Include -I$(pythonhome)/PC \\" - print "\t\t$(cflags) $(cdebug) $(cinclude) \\" - print '\t\t"%s"' % file - print - - print "$(target).exe:", - for obj in objects: print obj, - print - print "\tlink -out:$(target).exe", - for obj in objects: print obj, - print "\\" - print "\t\t$(pythonlib) $(lcustom) shell32.lib comdlg32.lib wsock32.lib \\" - print "\t\t-subsystem:$(subsystem) $(resources)" + libs = ["shell32.lib", "comdlg32.lib", "wsock32.lib", "user32.lib"] + for moddefn in moddefns: + print "# Module", moddefn.name + for file in moddefn.sourceFiles: + base = os.path.basename(file) + base, ext = os.path.splitext(base) + objects.append(base + ".obj") + print '%s.obj: "%s"' % (base, file) + print "\t@$(CC) -c -nologo $(cdl) /D BUILD_FREEZE", + print "-I$(pythonhome)/Include -I$(pythonhome)/PC \\" + print "\t\t$(cflags) $(cdebug) $(cinclude) \\" + extra = moddefn.GetCompilerOptions() + if extra: + print "\t\t%s \\" % (string.join(extra),) + print '\t\t"%s"' % file + print + + # Add .lib files this module needs + for modlib in moddefn.GetLinkerLibs(): + if modlib not in libs: + libs.append(modlib) + + print "ADDN_LINK_FILES=", + for addn in vars['addn_link']: print '"%s"' % (addn), + print ; print + + print "OBJS=", + for obj in objects: print '"%s"' % (obj), + print ; print + + print "LIBS=", + for lib in libs: print '"%s"' % (lib), + print ; print + + print "$(target)%s: $(OBJS)" % (target_ext) + print "\tlink -out:$(target)%s %s" % (target_ext, target_link_flags), + print "\t$(OBJS) \\" + print "\t$(LIBS) \\" + print "\t$(ADDN_LINK_FILES) \\" + print "\t\t$(pythonlib) $(lcustom)\\" + print "\t\t$(resources)" print print "clean:" - print "\t\t-rm *.obj" - print "\t\t-rm $(target).exe" + print "\t-rm -f *.obj" + print "\t-rm -f $(target).exe" # Local Variables: # indent-tabs-mode: nil -- cgit v0.12