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 = 0
     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         if self.bdist_dir is None:
     84             if self.skip_build and self.plat_name:
     85                 # If build is skipped and plat_name is overridden, bdist will

     86                 # not see the correct 'plat_name' - so set that up manually.

     87                 bdist = self.distribution.get_command_obj('bdist')
     88                 bdist.plat_name = self.plat_name
     89                 # next the command will be initialized using that name

     90             bdist_base = self.get_finalized_command('bdist').bdist_base
     91             self.bdist_dir = os.path.join(bdist_base, 'wininst')
     92         if not self.target_version:
     93             self.target_version = ""
     94         if not self.skip_build and self.distribution.has_ext_modules():
     95             short_version = get_python_version()
     96             if self.target_version and self.target_version != short_version:
     97                 raise DistutilsOptionError, \
     98                       "target version can only be %s, or the '--skip-build'" \
     99                       " option must be specified" % (short_version,)
    100             self.target_version = short_version
    101 
    102         self.set_undefined_options('bdist',
    103                                    ('dist_dir', 'dist_dir'),
    104                                    ('plat_name', 'plat_name'),
    105                                   )
    106 
    107         if self.install_script:
    108             for script in self.distribution.scripts:
    109                 if self.install_script == os.path.basename(script):
    110                     break
    111             else:
    112                 raise DistutilsOptionError, \
    113                       "install_script '%s' not found in scripts" % \
    114                       self.install_script
    115     # finalize_options()

    116 
    117 
    118     def run (self):
    119         if (sys.platform != "win32" and
    120             (self.distribution.has_ext_modules() or
    121              self.distribution.has_c_libraries())):
    122             raise DistutilsPlatformError \
    123                   ("distribution contains extensions and/or C libraries; "
    124                    "must be compiled on a Windows 32 platform")
    125 
    126         if not self.skip_build:
    127             self.run_command('build')
    128 
    129         install = self.reinitialize_command('install', reinit_subcommands=1)
    130         install.root = self.bdist_dir
    131         install.skip_build = self.skip_build
    132         install.warn_dir = 0
    133         install.plat_name = self.plat_name
    134 
    135         install_lib = self.reinitialize_command('install_lib')
    136         # we do not want to include pyc or pyo files

    137         install_lib.compile = 0
    138         install_lib.optimize = 0
    139 
    140         if self.distribution.has_ext_modules():
    141             # If we are building an installer for a Python version other

    142             # than the one we are currently running, then we need to ensure

    143             # our build_lib reflects the other Python version rather than ours.

    144             # Note that for target_version!=sys.version, we must have skipped the

    145             # build step, so there is no issue with enforcing the build of this

    146             # version.

    147             target_version = self.target_version
    148             if not target_version:
    149                 assert self.skip_build, "Should have already checked this"
    150                 target_version = sys.version[0:3]
    151             plat_specifier = ".%s-%s" % (self.plat_name, target_version)
    152             build = self.get_finalized_command('build')
    153             build.build_lib = os.path.join(build.build_base,
    154                                            'lib' + plat_specifier)
    155 
    156         # Use a custom scheme for the zip-file, because we have to decide

    157         # at installation time which scheme to use.

    158         for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'):
    159             value = string.upper(key)
    160             if key == 'headers':
    161                 value = value + '/Include/$dist_name'
    162             setattr(install,
    163                     'install_' + key,
    164                     value)
    165 
    166         log.info("installing to %s", self.bdist_dir)
    167         install.ensure_finalized()
    168 
    169         # avoid warning of 'install_lib' about installing

    170         # into a directory not in sys.path

    171         sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB'))
    172 
    173         install.run()
    174 
    175         del sys.path[0]
    176 
    177         # And make an archive relative to the root of the

    178         # pseudo-installation tree.

    179         from tempfile import mktemp
    180         archive_basename = mktemp()
    181         fullname = self.distribution.get_fullname()
    182         arcname = self.make_archive(archive_basename, "zip",
    183                                     root_dir=self.bdist_dir)
    184         # create an exe containing the zip-file

    185         self.create_exe(arcname, fullname, self.bitmap)
    186         if self.distribution.has_ext_modules():
    187             pyversion = get_python_version()
    188         else:
    189             pyversion = 'any'
    190         self.distribution.dist_files.append(('bdist_wininst', pyversion,
    191                                              self.get_installer_filename(fullname)))
    192         # remove the zip-file again

    193         log.debug("removing temporary file '%s'", arcname)
    194         os.remove(arcname)
    195 
    196         if not self.keep_temp:
    197             remove_tree(self.bdist_dir, dry_run=self.dry_run)
    198 
    199     # run()

    200 
    201     def get_inidata (self):
    202         # Return data describing the installation.

    203 
    204         lines = []
    205         metadata = self.distribution.metadata
    206 
    207         # Write the [metadata] section.

    208         lines.append("[metadata]")
    209 
    210         # 'info' will be displayed in the installer's dialog box,

    211         # describing the items to be installed.

    212         info = (metadata.long_description or '') + '\n'
    213 
    214         # Escape newline characters

    215         def escape(s):
    216             return string.replace(s, "\n", "\\n")
    217 
    218         for name in ["author", "author_email", "description", "maintainer",
    219                      "maintainer_email", "name", "url", "version"]:
    220             data = getattr(metadata, name, "")
    221             if data:
    222                 info = info + ("\n    %s: %s" % \
    223                                (string.capitalize(name), escape(data)))
    224                 lines.append("%s=%s" % (name, escape(data)))
    225 
    226         # The [setup] section contains entries controlling

    227         # the installer runtime.

    228         lines.append("\n[Setup]")
    229         if self.install_script:
    230             lines.append("install_script=%s" % self.install_script)
    231         lines.append("info=%s" % escape(info))
    232         lines.append("target_compile=%d" % (not self.no_target_compile))
    233         lines.append("target_optimize=%d" % (not self.no_target_optimize))
    234         if self.target_version:
    235             lines.append("target_version=%s" % self.target_version)
    236         if self.user_access_control:
    237             lines.append("user_access_control=%s" % self.user_access_control)
    238 
    239         title = self.title or self.distribution.get_fullname()
    240         lines.append("title=%s" % escape(title))
    241         import time
    242         import distutils
    243         build_info = "Built %s with distutils-%s" % \
    244                      (time.ctime(time.time()), distutils.__version__)
    245         lines.append("build_info=%s" % build_info)
    246         return string.join(lines, "\n")
    247 
    248     # get_inidata()

    249 
    250     def create_exe (self, arcname, fullname, bitmap=None):
    251         import struct
    252 
    253         self.mkpath(self.dist_dir)
    254 
    255         cfgdata = self.get_inidata()
    256 
    257         installer_name = self.get_installer_filename(fullname)
    258         self.announce("creating %s" % installer_name)
    259 
    260         if bitmap:
    261             bitmapdata = open(bitmap, "rb").read()
    262             bitmaplen = len(bitmapdata)
    263         else:
    264             bitmaplen = 0
    265 
    266         file = open(installer_name, "wb")
    267         file.write(self.get_exe_bytes())
    268         if bitmap:
    269             file.write(bitmapdata)
    270 
    271         # Convert cfgdata from unicode to ascii, mbcs encoded

    272         try:
    273             unicode
    274         except NameError:
    275             pass
    276         else:
    277             if isinstance(cfgdata, unicode):
    278                 cfgdata = cfgdata.encode("mbcs")
    279 
    280         # Append the pre-install script

    281         cfgdata = cfgdata + "\0"
    282         if self.pre_install_script:
    283             script_data = open(self.pre_install_script, "r").read()
    284             cfgdata = cfgdata + script_data + "\n\0"
    285         else:
    286             # empty pre-install script

    287             cfgdata = cfgdata + "\0"
    288         file.write(cfgdata)
    289 
    290         # The 'magic number' 0x1234567B is used to make sure that the

    291         # binary layout of 'cfgdata' is what the wininst.exe binary

    292         # expects.  If the layout changes, increment that number, make

    293         # the corresponding changes to the wininst.exe sources, and

    294         # recompile them.

    295         header = struct.pack("<iii",
    296                              0x1234567B,       # tag

    297                              len(cfgdata),     # length

    298                              bitmaplen,        # number of bytes in bitmap

    299                              )
    300         file.write(header)
    301         file.write(open(arcname, "rb").read())
    302 
    303     # create_exe()

    304 
    305     def get_installer_filename(self, fullname):
    306         # Factored out to allow overriding in subclasses

    307         if self.target_version:
    308             # if we create an installer for a specific python version,

    309             # it's better to include this in the name

    310             installer_name = os.path.join(self.dist_dir,
    311                                           "%s.%s-py%s.exe" %
    312                                            (fullname, self.plat_name, self.target_version))
    313         else:
    314             installer_name = os.path.join(self.dist_dir,
    315                                           "%s.%s.exe" % (fullname, self.plat_name))
    316         return installer_name
    317     # get_installer_filename()

    318 
    319     def get_exe_bytes (self):
    320         from distutils.msvccompiler import get_build_version
    321         # If a target-version other than the current version has been

    322         # specified, then using the MSVC version from *this* build is no good.

    323         # Without actually finding and executing the target version and parsing

    324         # its sys.version, we just hard-code our knowledge of old versions.

    325         # NOTE: Possible alternative is to allow "--target-version" to

    326         # specify a Python executable rather than a simple version string.

    327         # We can then execute this program to obtain any info we need, such

    328         # as the real sys.version string for the build.

    329         cur_version = get_python_version()
    330         if self.target_version and self.target_version != cur_version:
    331             # If the target version is *later* than us, then we assume they

    332             # use what we use

    333             # string compares seem wrong, but are what sysconfig.py itself uses

    334             if self.target_version > cur_version:
    335                 bv = get_build_version()
    336             else:
    337                 if self.target_version < "2.4":
    338                     bv = 6.0
    339                 else:
    340                     bv = 7.1
    341         else:
    342             # for current version - use authoritative check.

    343             bv = get_build_version()
    344 
    345         # wininst-x.y.exe is in the same directory as this file

    346         directory = os.path.dirname(__file__)
    347         # we must use a wininst-x.y.exe built with the same C compiler

    348         # used for python.  XXX What about mingw, borland, and so on?

    349 
    350         # if plat_name starts with "win" but is not "win32"

    351         # we want to strip "win" and leave the rest (e.g. -amd64)

    352         # for all other cases, we don't want any suffix

    353         if self.plat_name != 'win32' and self.plat_name[:3] == 'win':
    354             sfix = self.plat_name[3:]
    355         else:
    356             sfix = ''
    357 
    358         filename = os.path.join(directory, "wininst-%.1f%s.exe" % (bv, sfix))
    359         f = open(filename, "rb")
    360         try:
    361             return f.read()
    362         finally:
    363             f.close()
    364 # class bdist_wininst

    365