BDistInno.py :  » XML » 4Suite » 4Suite-XML-1.0.2 » Ft » Lib » DistExt » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » XML » 4Suite 
4Suite » 4Suite XML 1.0.2 » Ft » Lib » DistExt » BDistInno.py
import os
import sys
import zipfile
from distutils import util
from distutils.core import Command
from distutils.errors import DistutilsInternalError,DistutilsPlatformError
from distutils.dir_util import remove_tree
from distutils.sysconfig import get_python_version

INNO_MIN_VERSION = '5.1.5'
INNO_MAX_VERSION = '5.1.7'

PY_SOURCE_EXTS = ('.py', '.pyw')

ISCC_TEMPLATE = r"""
[Setup]
OutputDir=%(output-dir)s
OutputBaseFilename=%(output-basename)s
Compression=lzma
SolidCompression=yes
AppName=%(name)s
AppVersion=%(version)s
AppVerName=%(name)s %(version)s for Python %(target-version)s
AppId=%(name)s-%(target-version)s
AppPublisher=%(publisher)s
AppPublisherURL=%(publisher-url)s
AppSupportURL=%(support-url)s
UninstallFilesDir=%(uninstall-dir)s
DefaultDirName={code:GetDefaultDir}
DefaultGroupName={code:GetDefaultGroup}
LicenseFile=%(license-file)s
UserInfoPage=no
DisableReadyMemo=yes
DirExistsWarning=no
AppendDefaultDirName=no

[Types]
Name: "full"; Description: "Full installation"
Name: "compact"; Description: "Compact installation"
Name: "custom"; Description: "Custom installation"; Flags: iscustom

[Components]
%(components)s

%(sections)s

[Code]
{ Define parameters as constants to easy sharing between the script file
  and the template in BDistInno.py }
const
  GroupName = '%(name)s';
  TargetVersion = '%(target-version)s';

var
  PythonDir, PythonGroup: String;

function GetDefaultDir(Param: String): String;
begin
  Result := RemoveBackslashUnlessRoot(PythonDir);
end; { GetDefaultDir }

function GetDefaultGroup(Param: String): String;
begin
  Result := AddBackslash(PythonGroup) + GroupName;
end; { GetDefaultGroup }

procedure MutateConfigFile(Filename: String);
var
  Config: String;
  Prefix: String;
begin
  Filename := ExpandConstant(Filename);
  LoadStringFromFile(Filename, Config);
  Prefix := AddBackslash(WizardDirValue());
  StringChange(Prefix, '\', '\\')
  StringChange(Config, '\\PREFIX\\', Prefix);
  SaveStringToFile(Filename, Config, False);
end; { MutateConfigFile }

procedure InitializeSelectDirPage;
var
  Page: TWizardPage;
  Text: TLabel;
  Top, Left, Width: Integer;
begin
  Page := PageFromID(wpSelectDir);
  Top := WizardForm.DirEdit.Top + WizardForm.DirEdit.Height + 16;
  Left := WizardForm.SelectDirBrowseLabel.Left;
  Width := WizardForm.SelectDirBrowseLabel.Width;

  Text := TLabel.Create(Page);
  Text.Parent := Page.Surface;
  Text.Top := Top;
  Text.Left := Left;
  Text.Font.Style := [fsBold];
  Text.AutoSize := True;
  Text.WordWrap := True;
  Text.Width := Width;
  Text.Caption := 'Warning: A valid Python ' + TargetVersion +
                  ' installation could not be found.';
  Top := Top + Text.Height + 16;

  Text := TLabel.Create(Page);
  Text.Parent := Page.Surface;
  Text.Top := Top;
  Text.Left := Left;
  Text.AutoSize := True;
  Text.WordWrap := True;
  Text.Width := Width;
  Text.Caption := 'If you have a custom build of Python installed, select' +
                  ' the folder where it is installed as the installation' +
                  ' location.';
end; { InitializeSelectDirPage }

procedure InitializeWizard;
begin
  { Add customizations to the SelectDir page if Python is not found }
  if PythonDir = '' then
    InitializeSelectDirPage;
end; { InitializeWizard }

function InitializeSetup(): Boolean;
var
  Key: String;
begin
  { Get the default installation directory }
  Key := 'Software\Python\PythonCore\' + TargetVersion + '\InstallPath';
  if not RegQueryStringValue(HKEY_CURRENT_USER, Key, '', PythonDir) then
    RegQueryStringValue(HKEY_LOCAL_MACHINE, Key, '', PythonDir);

  { Get default Start Menu group }
  Key := Key + '\InstallGroup';
  if not RegQueryStringValue(HKEY_CURRENT_USER, Key, '', PythonGroup) then
    RegQueryStringValue(HKEY_LOCAL_MACHINE, Key, '', PythonGroup);

  Result := True;
end; { InitializeSetup }

function NextButtonClick(CurPage: Integer): Boolean;
begin
  Result := True;
  if CurPage = wpSelectDir then
  begin
    { Check that the install directory is part of PYTHONPATH }
  end
end; { NextButtonClick }
"""

class Section(object):
    section_name = None
    required_parameters = None
    optional_parameters = ['Languages', 'MinVersion', 'OnlyBelowVersion',
                           'BeforeInstall', 'AfterInstall']

    def __init__(self):
        assert self.section_name is not None, \
            "'section_name' must be defined"
        assert self.required_parameters is not None, \
            "'required_parameters' must be defined"
        self.entries = []

    def addEntry(self, **parameters):
        entry = []
        # Add the required parameters
        for parameter in self.required_parameters:
            try:
                value = parameters[parameter]
            except KeyError:
                raise DistutilsInternalError(
                    "missing required parameter '%s'" % parameter)
            else:
                del parameters[parameter]
            entry.append('%s: %s' % (parameter, value))
        # Add any optional parameters.
        for parameter in self.optional_parameters:
            if parameter in parameters:
                entry.append('%s: %s' % (parameter, parameters[parameter]))
                del parameters[parameter]
        # Any remaining parameters are errors.
        for parameter in parameters:
            raise DistutilsInternalError(
                "unsupported parameter '%s'" % parameter)
        # Create the entry string and store it.
        self.entries.append('; '.join(entry))
        return

class DirsSection(Section):
    section_name = 'Dirs'
    required_parameters = ['Name']
    optional_parameters = Section.optional_parameters + [
        'Attribs', 'Permissions', 'Flags']

class FilesSection(Section):
    section_name = 'Files'
    required_parameters = ['Source', 'DestDir']
    optional_parameters = Section.optional_parameters + [
        'DestName', 'Excludes', 'CopyMode', 'Attribs', 'Permissions',
        'FontInstall', 'Flags']

class IconsSection(Section):
    section_name = 'Icons'
    required_parameters = ['Name', 'Filename']
    optional_parameters = Section.optional_parameters + [
        'Parameters', 'WorkingDir', 'HotKey', 'Comment', 'IconFilename',
        'IconIndex', 'Flags']

class RunSection(Section):
    section_name = 'Run'
    required_parameters = ['Filename']
    optional_parameters = Section.optional_parameters + [
        'Description', 'Parameters', 'WorkingDir', 'StatusMsg', 'RunOnceId',
        'Flags']

class UninstallDeleteSection(Section):
    section_name = 'UninstallDelete'
    required_parameters = ['Type', 'Name']

class Component:
    section_mapping = {
        'Dirs' : DirsSection,
        'Files' : FilesSection,
        'Icons' : IconsSection,
        'Run' : RunSection,
        'UninstallDelete' : UninstallDeleteSection,
        }

    def __init__(self, name, description, types):
        self.name = name
        self.description = description
        self.types = types
        self.sections = {}

    def getEntry(self):
        return 'Name: "%s"; Description: "%s"; Types: %s' % (
            self.name, self.description, self.types)

    def hasEntries(self):
        for section in self.sections.itervalues():
            if section.entries:
                return True
        return False

    def getSection(self, name):
        if name not in self.sections:
            try:
                section_class = self.section_mapping[name]
            except KeyError:
                raise DistutilsInternalError("unknown section '%s'" % name)
            self.sections[name] = section_class()
        return self.sections[name]

    def getSectionEntries(self, name):
        return [ '%s; Components: %s' %(entry, self.name)
                 for entry in self.getSection(name).entries ]

class BDistInno(Command):

    command_name = 'bdist_inno'

    description = "create an executable installer for MS Windows"

    user_options = [
        ('bdist-dir=', None,
         "temporary directory for creating the distribution"),
        ('keep-temp', 'k',
         "keep the pseudo-installation tree around after " +
         "creating the distribution archive"),
        ('target-version=', None,
         "require a specific python version on the target system"),
        ('no-target-compile', 'c',
         "do not compile .py to .pyc on the target system"),
        ('no-target-optimize', 'o',
         "do not compile .py to .pyo (optimized) on the target system"),
        ('dist-dir=', 'd',
         "directory to put final built distributions in"),
        ('skip-build', None,
         "skip rebuilding everything (for testing/debugging)"),
        ]

    boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
                       'skip-build']

    def initialize_options(self):
        self.bdist_dir = None
        self.keep_temp = None
        self.target_version = None
        self.no_target_compile = None
        self.no_target_optimize = None
        self.dist_dir = None
        self.skip_build = None
        self.byte_compile = True
        return

    def finalize_options(self):
        if self.bdist_dir is None:
            bdist_base = self.get_finalized_command('bdist').bdist_base
            self.bdist_dir = os.path.join(bdist_base, 'inno')

        self.set_undefined_options('bdist',
                                   ('keep_temp', 'keep_temp'),
                                   ('dist_dir', 'dist_dir'),
                                   ('skip_build', 'skip_build'))

        if not self.target_version:
            self.target_version = get_python_version()
        if not self.skip_build and (self.distribution.has_ext_modules() or
                                    self.distribution.has_scripts()):
            short_version = get_python_version()
            if self.target_version != short_version:
                raise DistutilsOptionError(
                    "target version can only be %s, or the '--skip_build'"
                    " option must be specified" % short_version)
            self.target_version = short_version

        self.license_file = self.distribution.license_file
        if self.license_file:
            self.license_file = util.convert_path(self.license_file)

        self.output_basename = '%s.win32'
        return

    def run(self):
        if sys.platform != 'win32':
            raise DistutilsPlatformError("InnoSetup distributions must be"
                                         " created on a Windows platform")
        # Locate information about Inno Setup from the registry
        from _winreg import OpenKeyEx,QueryValueEx,HKEY_LOCAL_MACHINE
        try:
            key = OpenKeyEx(HKEY_LOCAL_MACHINE,
                            r'SOFTWARE\Microsoft\Windows'
                            r'\CurrentVersion\Uninstall'
                            r'\Inno Setup 5_is1')
            inno_version = QueryValueEx(key, 'DisplayVersion')[0]
            inno_path = QueryValueEx(key, 'InstallLocation')[0]
        except WindowsError:
            raise DistutilsPlatformError(
                'Inno Setup version %s to %s is required to build the'
                ' installer, but was not found on this system.' %
                (INNO_MIN_VERSION, INNO_MAX_VERSION))
        if inno_version < INNO_MIN_VERSION or inno_version > INNO_MAX_VERSION:
            raise DistutilsPlatformError(
                'Inno Setup version %s to %s is required to build the'
                ' installer, but version %s was found on this system.' %
                (INNO_MIN_VERSION, INNO_MAX_VERSION, inno_version))
        iss_compiler = os.path.join(inno_path, 'iscc.exe')

        if not self.skip_build:
            self.run_command('build')

        self.mkpath(self.bdist_dir)

        config = self.reinitialize_command('config')
        config.cache_filename = None
        config.prefix = 'PREFIX'
        config.ensure_finalized()

        install = self.reinitialize_command('install')
        install.root = self.bdist_dir
        install.skip_build = self.skip_build

        # Always include "compiled" py-files
        install.compile = install.optimize = self.byte_compile

        # don't warn about installing into a directory not in sys.path
        install.warn_dir = False

        # always include documentation
        install.with_docs = True

        self.announce("installing to %s" % self.bdist_dir, 2)
        install.ensure_finalized()
        install.run()

        if self.license_file:
            self.copy_file(self.license_file, self.bdist_dir)

        # create the InnoSetup script
        iss_file = self.build_iss_file()
        iss_path = os.path.join(self.bdist_dir,
                                '%s.iss' % self.distribution.get_name())
        self.announce("writing %r" % iss_path)
        if not self.dry_run:
            f = open(iss_path, "w")
            f.write(iss_file)
            f.close()

        # build distribution using the Inno Setup 5 Command-Line Compiler
        self.announce("creating Inno Setup installer", 2) # log.info
        self.spawn([iss_compiler, iss_path])

        # Add an empty ZIP file to the installer executable to allow for
        # uploading to PyPI (it checks for the bdist_wininst format).
        dist_filename = self.get_installer_filename()
        if os.path.exists(dist_filename) and not self.dry_run:
            install_egg_info = self.get_finalized_command('install_egg_info')
            install_dir = install_egg_info.install_dir
            zip_dir = 'PLATLIB'
            if install_dir.endswith(os.sep):
                zip_dir += os.sep
            zip_file = zipfile.ZipFile(dist_filename, 'a')
            for filename in install_egg_info.get_outputs():
                arcname = filename.replace(install_dir, zip_dir, 1)
                zip_file.write(filename, arcname)
            zip_file.close()

        # Add to 'Distribution.dist_files' so that the "upload" command works
        if hasattr(self.distribution, 'dist_files'):
            target_version = self.target_version or 'any'
            spec = ('bdist_wininst', target_version, dist_filename)
            self.distribution.dist_files.append(spec)

        if not self.keep_temp:
            remove_tree(self.bdist_dir, self.verbose, self.dry_run)
        return

    def build_iss_file(self):
        """Generate the text of an InnoSetup iss file and return it as a
        list of strings (one per line).
        """
        # [Icons]
        filespec = 'Source: "%s"; DestDir: "%s"; Components: %s'
        dirspec = 'Name: "%s"; Components: %s'
        uninstallspec = 'Type: files; Name: "%s"'
        iconspec = 'Name: "%s"; Filename: "%s"; Components: %s'
        runspec = ('Description: "%s"; Filename: "%s"; Components: %s; '
                   'Flags: %s')

        main_component = Component('Main',
                                   self.distribution.get_name() + ' Library',
                                   'full compact custom')
        docs_component = Component('Main\\Documentation', 'Documentation',
                                   'full')
        test_component = Component('Main\\Testsuite', 'Test suite', 'full')

        install = self.get_finalized_command('install')
        for command_name in install.get_sub_commands():
            command = self.get_finalized_command(command_name)
            # Get the mutated outputs split by type.
            dirs, files, uninstall = self._mutate_outputs(command)
            # Perform any command-specific processing
            if command_name == 'install_html':
                component = docs_component
                for document in command.documents:
                    flags = getattr(document, 'flags', ())
                    if 'postinstall' in flags:
                        section = component.getSection('Run')
                        filename = command.get_output_filename(document)
                        filename = self._mutate_filename(filename)[1]
                        section.addEntry(
                            Description='"View %s"' % document.title,
                            Filename='"%s"' % filename,
                            Flags='postinstall shellexec skipifsilent')
                    if 'shortcut' in flags:
                        section = component.getSection('Icons')
                        filename = command.get_output_filename(document)
                        filename = self._mutate_filename(filename)[1]
                        section.addEntry(
                            Name='"{group}\\%s"' % document.title,
                            Filename='"%s"' % filename)
            elif command_name == 'install_text':
                component = docs_component
            elif command_name == 'install_devel':
                component = test_component
            elif command_name == 'install_config':
                component = main_component
                section = component.getSection('Files')
                for source, destdir, extra in files:
                    dest = os.path.join(destdir, os.path.basename(source))
                    extra['AfterInstall'] = "MutateConfigFile('%s')" % dest
            else:
                component = main_component

            if dirs:
                section = component.getSection('Dirs')
                for name in dirs:
                    section.addEntry(Name='"%s"' % name)
            if files:
                section = component.getSection('Files')
                for source, destdir, extra in files:
                    section.addEntry(Source='"%s"' % source,
                                     DestDir='"%s"' % destdir,
                                     Flags='ignoreversion',
                                     **extra)
            if uninstall:
                section = component.getSection('UninstallDelete')
                for name in uninstall:
                    section.addEntry(Type='files',
                                     Name='"%s"' % name)

        components = []
        sections = {}
        for component in (main_component, docs_component, test_component):
            has_entries = False
            for section in component.sections:
                entries = component.getSectionEntries(section)
                if entries:
                    has_entries = True
                    if section not in sections:
                        sections[section] = ['[%s]' % section]
                    sections[section].extend(entries)
            if has_entries:
                components.append(component.getEntry())
        components = '\n'.join(components)

        for name in sections:
            sections[name] = '\n'.join(sections[name])
        sections = '\n\n'.join(sections.values())

        output_filename = self.get_installer_filename()
        output_dir, output_basename = os.path.split(output_filename)
        output_basename = os.path.splitext(output_basename)[0]
        uninstall_dir = os.path.join(install.install_localstate, 'Uninstall')
        _, uninstall_dir = self._mutate_filename(uninstall_dir)
        subst = {
            'output-dir' : os.path.abspath(output_dir),
            'output-basename' : output_basename,
            'name' : self.distribution.get_name(),
            'version' : self.distribution.get_version(),
            'publisher' : self.distribution.get_author(),
            'publisher-url' : self.distribution.get_author_email(),
            'support-url' : self.distribution.get_url(),
            'uninstall-dir' : uninstall_dir,
            'license-file' : os.path.basename(self.license_file or ''),
            'target-version' : sys.version[:3],
            'custom-page' : self.license_file and 'wpLicense' or 'wpWelcome',
            'components' : components,
            'sections' : sections,
            }

        return ISCC_TEMPLATE % subst

    def _mutate_filename(self, filename):
        # Strip the bdist_dir from the filename as the files will be
        # relative to the setup script which is in bdist_dir.  This
        # is to make the setup script more readable.
        source = filename[len(self.bdist_dir) + len(os.sep):]
        # Translate the filename to what is used in the setup script
        dest = source.replace('PREFIX', '{app}', 1)
        return source, dest

    def _mutate_outputs(self, command):
        dirs = []
        files = []
        uninstall = []
        compile = getattr(command, 'compile', 0)
        optimize = getattr(command, 'optimize', 0)
        for filename in command.get_outputs():
            source, dest = self._mutate_filename(filename)
            if os.path.isdir(filename):
                # An empty directory
                dirs.append(dest)
            else:
                files.append((source, os.path.dirname(dest), {}))
                # Add uninstall entries for possible bytecode files created
                # *after* installation.
                for extension in PY_SOURCE_EXTS:
                    if dest.endswith(extension):
                        barename = dest[:-len(extension)]
                        if not compile:
                            uninstall.append(barename + '.pyc')
                        if not optimize:
                            uninstall.append(barename + '.pyo')
        return dirs, files, uninstall

    def get_installer_filename(self):
        installer_name = '%s.win32' % self.distribution.get_fullname()
        if self.target_version:
            # if we create an installer for a specific python version,
            # include this in the name to match bdist_wininst.
            installer_name += '-py' + self.target_version
        return os.path.join(self.dist_dir, installer_name + '.exe')
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.