PageRenderTime 398ms CodeModel.GetById 131ms app.highlight 118ms RepoModel.GetById 145ms app.codeStats 0ms

/Lib/distutils/command/bdist_wininst.py

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