This is a minimal example of how BrianDorsey is building windows installers using Py2exe and InnoSetup.

The code borrows heavily from Thomas Heller's sample code for extending Py2exe.

Start with a Python program:

hello.py:

   1 print "Hello."
   2 print "This is my data:"
   3 print open('data.txt').read()

and a datafile:

data.txt:

DATA, DATA, DATA, DATA, DATA, DATA, DATA, 
DATA, DATA, DATA, DATA. 

Now, create a setup.py which will used py2exe to build executables, and extend the py2exe build step so that we can create an installer which will automatically include all of the files which py2exe outputs.

setup.py

   1 from distutils.core import setup
   2 import py2exe
   3 import os
   4 
   5 __version__ = "0.0.1.0"
   6 
   7 
   8 # You'll have to set this to match your environment. 
   9 INNOSETUP_COMPILER = r'C:\InnoSetup\ISCC.exe'
  10 
  11 ############################################################
  12 class InnoScript:
  13     def __init__(self,
  14                  name,
  15                  lib_dir,
  16                  dist_dir,
  17                  console_exe_files = [],
  18                  lib_files = [],
  19                  version = "1.0"):
  20         self.lib_dir = lib_dir
  21         self.dist_dir = dist_dir
  22         if not self.dist_dir[-1] in "\\/":
  23             self.dist_dir += "\\"
  24         self.name = name
  25         self.version = version
  26         self.console_exe_files = [self.chop(p) for p in console_exe_files]
  27         self.lib_files = [self.chop(p) for p in lib_files]
  28 
  29     def chop(self, pathname):
  30         assert pathname.startswith(self.dist_dir)
  31         return pathname[len(self.dist_dir):]
  32 
  33     def create(self, pathname=None):
  34         """ Create the Inno Setup configuration script. """
  35         if not pathname:
  36             pathname = os.path.join(self.dist_dir, self.name) + '.iss'
  37         print "InnoSetup script: %s" % pathname
  38         self.pathname = pathname
  39         ofi = self.file = open(pathname, "w")
  40         print >> ofi, "; WARNING: This script has been created by py2exe. Changes to this script"
  41         print >> ofi, "; will be overwritten the next time py2exe is run!\n"
  42         print >> ofi, r"[Setup]"
  43         print >> ofi, r"AppName=%s" % self.name
  44         print >> ofi, r"AppVersion=%s" % (self.version)
  45         print >> ofi, r"AppVerName=%s %s" % (self.name, self.version)
  46         print >> ofi, r"AppPublisher=Your Name Here."
  47         print >> ofi, r"DefaultDirName={pf}\%s" % self.name
  48         print >> ofi, r"DefaultGroupName=%s" % self.name
  49         print >> ofi, r"Compression=lzma"
  50         print >> ofi, r"OutputDir=%s" % self.dist_dir
  51         print >> ofi, r"OutputBaseFilename=%s_%s_Setup" % (self.name,
  52                 self.version)
  53         print >> ofi
  54 
  55         print >> ofi, r"[Files]"
  56         for path in self.console_exe_files + self.lib_files:
  57             flags = 'ignoreversion'
  58             if path.lower().endswith('.cfg'):
  59                 flags += ' onlyifdoesntexist '
  60             print >> ofi, r'Source: "%s"; DestDir: "{app}\%s"; Flags: %s' % (path, os.path.dirname(path), flags)
  61         print >> ofi
  62 
  63         ofi.close()
  64 
  65     def compile(self):
  66         cmd = "%s %s" % (INNOSETUP_COMPILER, self.pathname)
  67         print "Running InnoSetup: %s" % cmd
  68         errorlevel = os.system(cmd)
  69         print "InnoSetup returned errorlevel: %s" % errorlevel
  70 
  71 
  72 ################################################################
  73 
  74 from py2exe.build_exe import py2exe
  75 
  76 class debug_installer(py2exe):
  77     """A replacement for the default py2exe installer which prints out
  78     all of the instance variables after py2exe completes."""
  79     def run(self):
  80         # First, let py2exe do it's work.
  81         py2exe.run(self)
  82         from pprint import pprint
  83         print "#" * 79
  84         print "#" * 79
  85         pprint(vars(self))
  86 
  87 class build_installer(py2exe):
  88     """This class first builds the exe file(s), then creates a Windows installer.
  89 
  90     You need InnoSetup for it."""
  91     def run(self):
  92         # First, let py2exe do it's work.
  93         py2exe.run(self)
  94 
  95         lib_dir = self.lib_dir
  96         dist_dir = self.dist_dir
  97 
  98         # create the Installer, using the files py2exe has created.
  99         script = InnoScript("Hello", #The Setup product name
 100                             lib_dir,
 101                             dist_dir,
 102                             self.console_exe_files,
 103                             self.lib_files,
 104                             __version__)
 105         print "*** creating the inno setup script***"
 106         script.create()
 107         print "*** compiling the inno setup script***"
 108         script.compile()
 109 
 110 
 111 ################################################################
 112 ## Main setup
 113 
 114 excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon",
 115 "pywin.dialogs", "pywin.dialogs.list", "os.path", 'mx', 'mx.DateTime']
 116 includes = []
 117 
 118 class Target:
 119     def __init__(self, **kw):
 120         # for the versioninfo resources
 121         self.name = "Hello"
 122         self.description = "A simple app to demo py2exe and Inno Setup."
 123         self.company_name = ""
 124         self.copyright = ""
 125         self.version = __version__
 126         self.__dict__.update(kw)
 127 
 128 
 129 setup(
 130     options = {"py2exe": {
 131         "excludes": excludes,
 132         "includes": includes}},
 133     zipfile = "lib/shared.lib",
 134     console = [ Target(script = "hello.py",
 135                     name = "Hello",
 136                     description = "Monitors directories and updates MLC."),
 137                 ],
 138     data_files = [('',['data.txt'])
 139                 ],
 140     cmdclass = {"py2exe": build_installer},
 141     )

Running python setup.py py2exe should create a dist directory containing the usual output from py2exe, an InnoSetup config file and the actual installer executable. {{{C:\Data\BrianDocuments\home\python\Py2exeInnoDemo>dir dist

12/09/2004 01:42p <DIR> . 12/09/2004 01:42p <DIR> .. 12/09/2004 01:25p 70 data.txt 12/09/2004 01:42p 20,480 hello.exe 12/09/2004 01:42p 816 Hello.iss 12/09/2004 01:42p 699,942 Hello_0.0.1.0_Setup.exe 12/09/2004 12:52p <DIR> lib 05/25/2004 08:17p 974,909 python23.dll

}}}

Yea! You can now distribute Hello_0.0.1.0_Setup.exe and your users will be amazed! Have fun!

For reference, the generated Hello.iss file should look something like this: {{{; WARNING: This script has been created by py2exe. Changes to this script ; will be overwritten the next time py2exe is run!

[Setup] AppName=Hello AppVersion=0.0.1.0 AppVerName=Hello 0.0.1.0 AppPublisher=Your Name Here. DefaultDirName={pf}\Hello DefaultGroupName=Hello Compression=lzma OutputDir=C:\Data\BrianDocuments\home\python\Py2exeInnoDemo\dist\ OutputBaseFilename=Hello_0.0.1.0_Setup

[Files] Source: "hello.exe"; DestDir: "{app}\"; Flags: ignoreversion Source: "lib\_sre.pyd"; DestDir: "{app}\lib"; Flags: ignoreversion Source: "lib\shared.lib"; DestDir: "{app}\lib"; Flags: ignoreversion Source: "lib\w9xpopen.exe"; DestDir: "{app}\lib"; Flags: ignoreversion Source: "python23.dll"; DestDir: "{app}\"; Flags: ignoreversion Source: "data.txt"; DestDir: "{app}\"; Flags: ignoreversion }}}

This configuration barely scratches the surface of what InnoSetup can do. See the Inno Setup website for more info.

CreatingSetupFromPy2exe (last edited 2008-03-04 08:33:10 by localhost)