Home | History | Annotate | Download | only in command
      1 """distutils.command.bdist_wininst
      2 
      3 Implements the Distutils 'bdist_wininst' command: create a windows installer
      4 exe-program."""
      5 
      6 __revision__ = "$Id$"
      7 
      8 import sys
      9 import os
     10 import string
     11 
     12 from sysconfig import get_python_version
     13 
     14 from distutils.core import Command
     15 from distutils.dir_util import remove_tree
     16 from distutils.errors import DistutilsOptionError, DistutilsPlatformError
     17 from distutils import log
     18 from distutils.util import get_platform
     19 
     20 class bdist_wininst (Command):
     21 
     22     description = "create an executable installer for MS Windows"
     23 
     24     user_options = [('bdist-dir=', None,
     25                      "temporary directory for creating the distribution"),
     26                     ('plat-name=', 'p',
     27                      "platform name to embed in generated filenames "
     28                      "(default: %s)" % get_platform()),
     29                     ('keep-temp', 'k',
     30                      "keep the pseudo-installation tree around after " +
     31                      "creating the distribution archive"),
     32                     ('target-version=', None,
     33                      "require a specific python version" +
     34                      " on the target system"),
     35                     ('no-target-compile', 'c',
     36                      "do not compile .py to .pyc on the target system"),
     37                     ('no-target-optimize', 'o',
     38                      "do not compile .py to .pyo (optimized)"
     39                      "on the target system"),
     40                     ('dist-dir=', 'd',
     41                      "directory to put final built distributions in"),
     42                     ('bitmap=', 'b',
     43                      "bitmap to use for the installer instead of python-powered logo"),
     44                     ('title=', 't',
     45                      "title to display on the installer background instead of default"),
     46                     ('skip-build', None,
     47                      "skip rebuilding everything (for testing/debugging)"),
     48                     ('install-script=', None,
     49                      "basename of installation script to be run after"
     50                      "installation or before deinstallation"),
     51                     ('pre-install-script=', None,
     52                      "Fully qualified filename of a script to be run before "
     53                      "any files are installed.  This script need not be in the "
     54                      "distribution"),
     55                     ('user-access-control=', None,
     56                      "specify Vista's UAC handling - 'none'/default=no "
     57                      "handling, 'auto'=use UAC if target Python installed for "
     58                      "all users, 'force'=always use UAC"),
     59                    ]
     60 
     61     boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
     62                        'skip-build']
     63 
     64     def initialize_options (self):
     65         self.bdist_dir = None
     66         self.plat_name = None
     67         self.keep_temp = 0
     68         self.no_target_compile = 0
     69         self.no_target_optimize = 0
     70         self.target_version = None
     71         self.dist_dir = None
     72         self.bitmap = None
     73         self.title = None
     74         self.skip_build = None
     75         self.install_script = None
     76         self.pre_install_script = None
     77         self.user_access_control = None
     78 
     79     # initialize_options()
     80 
     81 
     82     def finalize_options (self):
     83         self.set_undefined_options('bdist', ('skip_build', 'skip_build'))
     84 
     85         if self.bdist_dir is None:
     86             if self.skip_build and self.plat_name:
     87                 # If build is skipped and plat_name is overridden, bdist will
     88                 # not see the correct 'plat_name' - so set that up manually.
     89                 bdist = self.distribution.get_command_obj('bdist')
     90                 bdist.plat_name = self.plat_name
     91                 # next the command will be initialized using that name
     92             bdist_base = self.get_finalized_command('bdist').bdist_base
     93             self.bdist_dir = os.path.join(bdist_base, 'wininst')
     94 
     95         if not self.target_version:
     96             self.target_version = ""
     97 
     98         if not self.skip_build and self.distribution.has_ext_modules():
     99             short_version = get_python_version()
    100             if self.target_version and self.target_version != short_version:
    101                 raise DistutilsOptionError, \
    102                       "target version can only be %s, or the '--skip-build'" \
    103                       " option must be specified" % (short_version,)
    104             self.target_version = short_version
    105 
    106         self.set_undefined_options('bdist',
    107                                    ('dist_dir', 'dist_dir'),
    108                                    ('plat_name', 'plat_name'),
    109                                   )
    110 
    111         if self.install_script:
    112             for script in self.distribution.scripts:
    113                 if self.install_script == os.path.basename(script):
    114                     break
    115             else:
    116                 raise DistutilsOptionError, \
    117                       "install_script '%s' not found in scripts" % \
    118                       self.install_script
    119     # finalize_options()
    120 
    121 
    122     def run (self):
    123         if (sys.platform != "win32" and
    124             (self.distribution.has_ext_modules() or
    125              self.distribution.has_c_libraries())):
    126             raise DistutilsPlatformError \
    127                   ("distribution contains extensions and/or C libraries; "
    128                    "must be compiled on a Windows 32 platform")
    129 
    130         if not self.skip_build:
    131             self.run_command('build')
    132 
    133         install = self.reinitialize_command('install', reinit_subcommands=1)
    134         install.root = self.bdist_dir
    135         install.skip_build = self.skip_build
    136         install.warn_dir = 0
    137         install.plat_name = self.plat_name
    138 
    139         install_lib = self.reinitialize_command('install_lib')
    140         # we do not want to include pyc or pyo files
    141         install_lib.compile = 0
    142         install_lib.optimize = 0
    143 
    144         if self.distribution.has_ext_modules():
    145             # If we are building an installer for a Python version other
    146             # than the one we are currently running, then we need to ensure
    147             # our build_lib reflects the other Python version rather than ours.
    148             # Note that for target_version!=sys.version, we must have skipped the
    149             # build step, so there is no issue with enforcing the build of this
    150             # version.
    151             target_version = self.target_version
    152             if not target_version:
    153                 assert self.skip_build, "Should have already checked this"
    154                 target_version = sys.version[0:3]
    155             plat_specifier = ".%s-%s" % (self.plat_name, target_version)
    156             build = self.get_finalized_command('build')
    157             build.build_lib = os.path.join(build.build_base,
    158                                            'lib' + plat_specifier)
    159 
    160         # Use a custom scheme for the zip-file, because we have to decide
    161         # at installation time which scheme to use.
    162         for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'):
    163             value = string.upper(key)
    164             if key == 'headers':
    165                 value = value + '/Include/$dist_name'
    166             setattr(install,
    167                     'install_' + key,
    168                     value)
    169 
    170         log.info("installing to %s", self.bdist_dir)
    171         install.ensure_finalized()
    172 
    173         # avoid warning of 'install_lib' about installing
    174         # into a directory not in sys.path
    175         sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB'))
    176 
    177         install.run()
    178 
    179         del sys.path[0]
    180 
    181         # And make an archive relative to the root of the
    182         # pseudo-installation tree.
    183         from tempfile import mktemp
    184         archive_basename = mktemp()
    185         fullname = self.distribution.get_fullname()
    186         arcname = self.make_archive(archive_basename, "zip",
    187                                     root_dir=self.bdist_dir)
    188         # create an exe containing the zip-file
    189         self.create_exe(arcname, fullname, self.bitmap)
    190         if self.distribution.has_ext_modules():
    191             pyversion = get_python_version()
    192         else:
    193             pyversion = 'any'
    194         self.distribution.dist_files.append(('bdist_wininst', pyversion,
    195                                              self.get_installer_filename(fullname)))
    196         # remove the zip-file again
    197         log.debug("removing temporary file '%s'", arcname)
    198         os.remove(arcname)
    199 
    200         if not self.keep_temp:
    201             remove_tree(self.bdist_dir, dry_run=self.dry_run)
    202 
    203     # run()
    204 
    205     def get_inidata (self):
    206         # Return data describing the installation.
    207 
    208         lines = []
    209         metadata = self.distribution.metadata
    210 
    211         # Write the [metadata] section.
    212         lines.append("[metadata]")
    213 
    214         # 'info' will be displayed in the installer's dialog box,
    215         # describing the items to be installed.
    216         info = (metadata.long_description or '') + '\n'
    217 
    218         # Escape newline characters
    219         def escape(s):
    220             return string.replace(s, "\n", "\\n")
    221 
    222         for name in ["author", "author_email", "description", "maintainer",
    223                      "maintainer_email", "name", "url", "version"]:
    224             data = getattr(metadata, name, "")
    225             if data:
    226                 info = info + ("\n    %s: %s" % \
    227                                (string.capitalize(name), escape(data)))
    228                 lines.append("%s=%s" % (name, escape(data)))
    229 
    230         # The [setup] section contains entries controlling
    231         # the installer runtime.
    232         lines.append("\n[Setup]")
    233         if self.install_script:
    234             lines.append("install_script=%s" % self.install_script)
    235         lines.append("info=%s" % escape(info))
    236         lines.append("target_compile=%d" % (not self.no_target_compile))
    237         lines.append("target_optimize=%d" % (not self.no_target_optimize))
    238         if self.target_version:
    239             lines.append("target_version=%s" % self.target_version)
    240         if self.user_access_control:
    241             lines.append("user_access_control=%s" % self.user_access_control)
    242 
    243         title = self.title or self.distribution.get_fullname()
    244         lines.append("title=%s" % escape(title))
    245         import time
    246         import distutils
    247         build_info = "Built %s with distutils-%s" % \
    248                      (time.ctime(time.time()), distutils.__version__)
    249         lines.append("build_info=%s" % build_info)
    250         return string.join(lines, "\n")
    251 
    252     # get_inidata()
    253 
    254     def create_exe (self, arcname, fullname, bitmap=None):
    255         import struct
    256 
    257         self.mkpath(self.dist_dir)
    258 
    259         cfgdata = self.get_inidata()
    260 
    261         installer_name = self.get_installer_filename(fullname)
    262         self.announce("creating %s" % installer_name)
    263 
    264         if bitmap:
    265             bitmapdata = open(bitmap, "rb").read()
    266             bitmaplen = len(bitmapdata)
    267         else:
    268             bitmaplen = 0
    269 
    270         file = open(installer_name, "wb")
    271         file.write(self.get_exe_bytes())
    272         if bitmap:
    273             file.write(bitmapdata)
    274 
    275         # Convert cfgdata from unicode to ascii, mbcs encoded
    276         try:
    277             unicode
    278         except NameError:
    279             pass
    280         else:
    281             if isinstance(cfgdata, unicode):
    282                 cfgdata = cfgdata.encode("mbcs")
    283 
    284         # Append the pre-install script
    285         cfgdata = cfgdata + "\0"
    286         if self.pre_install_script:
    287             script_data = open(self.pre_install_script, "r").read()
    288             cfgdata = cfgdata + script_data + "\n\0"
    289         else:
    290             # empty pre-install script
    291             cfgdata = cfgdata + "\0"
    292         file.write(cfgdata)
    293 
    294         # The 'magic number' 0x1234567B is used to make sure that the
    295         # binary layout of 'cfgdata' is what the wininst.exe binary
    296         # expects.  If the layout changes, increment that number, make
    297         # the corresponding changes to the wininst.exe sources, and
    298         # recompile them.
    299         header = struct.pack("<iii",
    300                              0x1234567B,       # tag
    301                              len(cfgdata),     # length
    302                              bitmaplen,        # number of bytes in bitmap
    303                              )
    304         file.write(header)
    305         file.write(open(arcname, "rb").read())
    306 
    307     # create_exe()
    308 
    309     def get_installer_filename(self, fullname):
    310         # Factored out to allow overriding in subclasses
    311         if self.target_version:
    312             # if we create an installer for a specific python version,
    313             # it's better to include this in the name
    314             installer_name = os.path.join(self.dist_dir,
    315                                           "%s.%s-py%s.exe" %
    316                                            (fullname, self.plat_name, self.target_version))
    317         else:
    318             installer_name = os.path.join(self.dist_dir,
    319                                           "%s.%s.exe" % (fullname, self.plat_name))
    320         return installer_name
    321     # get_installer_filename()
    322 
    323     def get_exe_bytes (self):
    324         from distutils.msvccompiler import get_build_version
    325         # If a target-version other than the current version has been
    326         # specified, then using the MSVC version from *this* build is no good.
    327         # Without actually finding and executing the target version and parsing
    328         # its sys.version, we just hard-code our knowledge of old versions.
    329         # NOTE: Possible alternative is to allow "--target-version" to
    330         # specify a Python executable rather than a simple version string.
    331         # We can then execute this program to obtain any info we need, such
    332         # as the real sys.version string for the build.
    333         cur_version = get_python_version()
    334         if self.target_version and self.target_version != cur_version:
    335             # If the target version is *later* than us, then we assume they
    336             # use what we use
    337             # string compares seem wrong, but are what sysconfig.py itself uses
    338             if self.target_version > cur_version:
    339                 bv = get_build_version()
    340             else:
    341                 if self.target_version < "2.4":
    342                     bv = 6.0
    343                 else:
    344                     bv = 7.1
    345         else:
    346             # for current version - use authoritative check.
    347             bv = get_build_version()
    348 
    349         # wininst-x.y.exe is in the same directory as this file
    350         directory = os.path.dirname(__file__)
    351         # we must use a wininst-x.y.exe built with the same C compiler
    352         # used for python.  XXX What about mingw, borland, and so on?
    353 
    354         # if plat_name starts with "win" but is not "win32"
    355         # we want to strip "win" and leave the rest (e.g. -amd64)
    356         # for all other cases, we don't want any suffix
    357         if self.plat_name != 'win32' and self.plat_name[:3] == 'win':
    358             sfix = self.plat_name[3:]
    359         else:
    360             sfix = ''
    361 
    362         filename = os.path.join(directory, "wininst-%.1f%s.exe" % (bv, sfix))
    363         f = open(filename, "rb")
    364         try:
    365             return f.read()
    366         finally:
    367             f.close()
    368 # class bdist_wininst
    369