[PATCH 1 of 1] Support for the distutils extention 'py2exe' added

Volker.Kleinfeld Volker.Kleinfeld at gmx.de
Wed Sep 7 16:42:11 CDT 2005


# HG changeset patch
# User Volker.Kleinfeld at gmx.de
# Node ID 49fba06e0d53be875383c70a4be0d058f34d088e
# Parent  c3cb9f39a91f893e803c011afb3e602531a0963d
Support for the distutils extention 'py2exe' added.

Description:
- If the py2exe distutils extention is installed this patch allows
  building standalone exe for windows - example:
  > python setup.py build --compiler=mingw32 py2exe
- The 'out of the box' py2exe is not able to resolve
  the dependencies due to 'demandload'. A new helper module
  of scanning the mercurial package has been added.

Changed:
- setup.py: importing py2exe and sub classing its command class
  to fetch the build directory and insert the needed includes
- packagescan.py: new helper module added, that scans the distutil
  build directory for modules to be included.

diff -r c3cb9f39a91f -r 49fba06e0d53 setup.py
--- a/setup.py	Mon Aug 29 19:15:37 2005
+++ b/setup.py	Wed Sep 07 18:48:52 2005
@@ -10,6 +10,34 @@
 from distutils.command.install_data import install_data
 
 import mercurial.version
+
+# py2exe needs to be installed to work
+try:
+    import py2exe 
+
+    # Due to the use of demandload py2exe is not finding the modules.
+    # packagescan.getmodules creates a list of modules included in 
+    # the mercurial package plus depdent modules.
+    import mercurial.packagescan 
+    from py2exe.build_exe import py2exe as build_exe 
+
+    class py2exe_for_demandload(build_exe):
+        """ overwrites the py2exe command class for getting the build
+        directory and for setting the 'includes' option."""
+        def initialize_options(self):
+            self.build_lib = None
+            build_exe.initialize_options(self)
+        def finalize_options(self):
+            # Get the build directory, ie. where to search for modules.
+            self.set_undefined_options('build',
+                                       ('build_lib', 'build_lib'))
+            # Sets the 'includes' option with the list of needed modules
+            if not self.includes:
+                self.includes = []
+            self.includes += mercurial.packagescan.getmodules(self.build_lib,'mercurial')
+            build_exe.finalize_options(self)
+except ImportError: pass
+
 
 # specify version string, otherwise 'hg identify' will be used:
 version = ''
@@ -36,7 +64,9 @@
                        ['templates/map'] +
                        glob.glob('templates/map-*') +
                        glob.glob('templates/*.tmpl'))],
-          cmdclass = { 'install_data' : install_package_data },
-          scripts=['hg', 'hgmerge'])
+          cmdclass = { 'install_data' : install_package_data,
+                       'py2exe' : py2exe_for_demandload},
+          scripts=['hg', 'hgmerge'],
+          console = ['hg'])
 finally:
     mercurial.version.forget_version()
diff -r c3cb9f39a91f -r 49fba06e0d53 mercurial/packagescan.py
--- /dev/null	Mon Aug 29 19:15:37 2005
+++ b/mercurial/packagescan.py	Wed Sep 07 18:48:52 2005
@@ -0,0 +1,74 @@
+# packagescan.py - Helper module for identifing used modules. 
+# Used for the py2exe distutil.
+#
+# Copyright 2005 Volker Kleinfeld <Volker.Kleinfeld at gmx.de>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+import glob
+import os
+import sys
+import demandload
+import ihooks
+
+requiredmodules = {} # Will contain the modules imported by demandload
+def demandload(scope, modules):
+    """ fake demandload function that collects the required modules """
+    for m in modules.split():
+        mod = None
+        mod = __import__(m,scope,scope)
+        scope[m] = mod
+        requiredmodules[mod.__name__] = 1
+
+def getmodules(libpath,packagename):
+    """ helper for finding all required modules of package <packagename> """
+    # Use the package in the build directory
+    libpath = os.path.abspath(libpath)
+    sys.path.insert(0,libpath)
+    packdir = os.path.join(libpath,packagename)
+    # A normal import would not find the package in 
+    # the build directory. ihook is used to force the import.
+    # After the package is imported the import scope for 
+    # the following imports is settled.
+    p = importfrom(packdir)
+    globals()[packagename] = p
+    sys.modules[packagename] = p
+    # Fetch the python modules in the package
+    cwd = os.getcwd()
+    os.chdir(packdir)
+    pymodulefiles = glob.glob('*.py')
+    extmodulefiles = glob.glob('*.pyd')
+    os.chdir(cwd)
+    # Install a fake demandload module
+    sys.modules['mercurial.demandload'] = sys.modules['mercurial.packagescan']
+    # Import all python modules and by that run the fake demandload
+    for m in pymodulefiles:
+        if m == '__init__.py': continue
+        tmp = {}
+        mname,ext = os.path.splitext(m)
+        fullname = packagename+'.'+mname 
+        __import__(fullname,tmp,tmp)
+        requiredmodules[fullname] = 1
+    # Import all extension modules and by that run the fake demandload
+    for m in extmodulefiles:
+        tmp = {}
+        mname,ext = os.path.splitext(m)
+        fullname = packagename+'.'+mname
+        __import__(fullname,tmp,tmp)
+        requiredmodules[fullname] = 1
+    includes = requiredmodules.keys()
+    return includes
+
+def importfrom(filename):
+    """
+    import module/package from a named file and returns the module.
+    It does not check on sys.modules or includes the module in the scope.
+    """
+    loader = ihooks.BasicModuleLoader()
+    path, file = os.path.split(filename)
+    name, ext  = os.path.splitext(file)
+    m = loader.find_module_in_dir(name, path)
+    if not m:
+        raise ImportError, name
+    m = loader.load_module(name, m)
+    return m


More information about the Mercurial mailing list