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

Volker.Kleinfeld Volker.Kleinfeld at gmx.de
Sun Sep 4 16:53:18 CDT 2005


# HG changeset patch
# User Volker.Kleinfeld at gmx.de
# Node ID 738a8180a66f6ee163ca189a51535848612138a3
# 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 738a8180a66f setup.py
--- a/setup.py	Mon Aug 29 19:15:37 2005
+++ b/setup.py	Sun Sep 04 10:48:56 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.
+    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
+            mercurial.packagescan.getModules(self.build_lib,'mercurial')
+            if not self.includes:
+                self.includes = []
+            self.includes += mercurial.packagescan.getModules(self.build_lib,'mercurial')
+            build_exe.finalize_options(self)
+except: 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 738a8180a66f mercurial/packagescan.py
--- /dev/null	Mon Aug 29 19:15:37 2005
+++ b/mercurial/packagescan.py	Sun Sep 04 10:48:56 2005
@@ -0,0 +1,71 @@
+# 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, imp
+
+required_modules = {} # 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
+        required_modules[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)
+    # Import the package:
+    # A normal import would not find the package in 
+    # the build directory. After the package is imported 
+    # the import scope for the following imports is settled
+    p = import_from(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 the 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)
+        required_modules[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)
+        required_modules[fullName] = 1
+    includes = required_modules.keys()
+    return includes
+
+def import_from(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