[Pymoul-svn] SF.net SVN: pymoul: [96] pymoul/trunk
Status: Alpha
Brought to you by:
tiran
|
From: <ti...@us...> - 2007-01-29 12:59:57
|
Revision: 96
http://pymoul.svn.sourceforge.net/pymoul/?rev=96&view=rev
Author: tiran
Date: 2007-01-29 04:59:51 -0800 (Mon, 29 Jan 2007)
Log Message:
-----------
Better InnoScript integration
Modified Paths:
--------------
pymoul/trunk/distutils_upx.py
Added Paths:
-----------
pymoul/trunk/distutils_iss.py
Copied: pymoul/trunk/distutils_iss.py (from rev 95, pymoul/trunk/distutils_upx.py)
===================================================================
--- pymoul/trunk/distutils_iss.py (rev 0)
+++ pymoul/trunk/distutils_iss.py 2007-01-29 12:59:51 UTC (rev 96)
@@ -0,0 +1,510 @@
+"""Distutils helper for creating and running InnoSetup Script files
+"""
+__author__ = "Christian Heimes"
+__version__ = "$Id"
+__revision__ = "$Revision$"
+
+import os
+import sys
+import re
+from distutils import log
+from fnmatch import fnmatch
+from subprocess import call as subcall
+from ConfigParser import SafeConfigParser
+from ConfigParser import NoSectionError
+from ConfigParser import DEFAULTSECT
+
+class ISSConfigParser(SafeConfigParser):
+ """Config parser with some extensions for ISS
+
+ new methods:
+ add_header(string) - Adds comments to the header
+ set_raw(section, string) - Adds a raw entry to a section
+ add_sectionif(section)
+ setif(section, option, value)
+
+ changed behavior:
+ doesn't write [default] section to file
+ interpolates "%(...)s" when writing to file
+ doesn't parse key: value
+ parses "Key: "value"; ..." to raw
+ writes sections in the order they are created
+
+ >>> from StringIO import StringIO
+ >>> defaults = {'appname' : 'Test App'}
+ >>> cfg = ISSConfigParser(defaults)
+ >>> cfg.add_header("header")
+
+ >>> cfg.add_section("testsection")
+ >>> cfg.set("testsection", "key", "value %(appname)s")
+ >>> cfg.set_raw("testsection", 'Rawline: "%(appname)s";')
+
+ >>> out = StringIO()
+ >>> cfg.write(out)
+ >>> out.seek(0)
+ >>> data = out.read()
+ >>> print data
+ ; header
+ [testsection]
+ key = value Test App
+ Rawline: "Test App";
+ <BLANKLINE>
+
+ >>> template = StringIO(data)
+ >>> del cfg, out, data
+ >>> cfg = ISSConfigParser(defaults)
+ >>> cfg.readfp(template)
+ >>> cfg._sections
+ {'testsection': {'__name__': 'testsection', 'key': 'value Test App'}}
+ >>> cfg._raw
+ {'testsection': ['Rawline: "Test App";']}
+
+ >>> out = StringIO()
+ >>> cfg.write(out)
+ >>> out.seek(0)
+ >>> data = out.read()
+ >>> print data
+ [testsection]
+ key = value Test App
+ Rawline: "Test App";
+ <BLANKLINE>
+
+ >>>
+ """
+ def __init__(self, defaults=None):
+ SafeConfigParser.__init__(self, defaults)
+ self._raw = {}
+ self._header = []
+ self._order = []
+
+ def add_header(self, value):
+ """Add a header comment
+ """
+ self._header.append(value)
+
+ def add_section(self, section):
+ """Create a new section in the configuration.
+ """
+ SafeConfigParser.add_section(self, section)
+ self._raw[section]= []
+ self._order.append(section)
+
+ def add_sectionif(self, section):
+ """Create a new section in the configuration if section doesn't exist.
+ """
+ if not self.has_section(section):
+ self.add_section(section)
+ return True
+
+ def setif(self, section, option, value):
+ """Set section-option to value if option is not yet set
+ """
+ if not self.has_option(section, option):
+ self.set(section, option, value)
+ return True
+
+ def set_raw(self, section, raw):
+ """Add a raw string to a section
+ """
+ try:
+ sec = self._raw[section]
+ except KeyError:
+ raise NoSectionError(section)
+ if isinstance(raw, (tuple, list)):
+ for r in raw:
+ sec.append(r)
+ else:
+ sec.append(raw)
+
+ def get_raw(self, section, raw=False, vars=None):
+ """Get all raw lines as string for a given section.
+
+ Interpolates %(var)s vars
+ """
+ d = self._defaults.copy()
+ try:
+ d.update(self._sections[section])
+ except KeyError:
+ if section != DEFAULTSECT:
+ raise NoSectionError(section)
+ # Update with the entry specific variables
+ if vars:
+ for key, value in vars.items():
+ d[self.optionxform(key)] = value
+ try:
+ rawdata = "\n".join(self._raw[section])
+ except KeyError:
+ return None
+
+ if raw:
+ return rawdata
+ else:
+ return self._interpolate(section, "RAWDATA", rawdata, d)
+
+ def optionxform(self, optionstr):
+ return optionstr
+
+ def write(self, fp):
+ """Write an .ini-format representation of the configuration state."""
+ for header in self._header:
+ fp.write("; %s\n" % header.replace('\n', '; \n'))
+ #if self._defaults:
+ # fp.write("[%s]\n" % DEFAULTSECT)
+ # for (key, value) in self._defaults.items():
+ # fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
+ # fp.write("\n")
+ for section in self._order:
+ fp.write("[%s]\n" % section)
+ for key in self._sections[section]:
+ if key == "__name__":
+ continue
+ value = self.get(section, key, raw=False)
+ fp.write("%s = %s\n" %
+ (key, str(value).replace('\n', '\n\t')))
+ rawdata = self.get_raw(section, raw=False)
+ if rawdata:
+ fp.write(rawdata)
+ fp.write("\n")
+
+ def remove_section(self, section):
+ """Remove a file section."""
+ existed = RawConfigParser.remove_section(self, section)
+ if existed:
+ del self._raw[section]
+ return existed
+
+ OPTCRE = re.compile(
+ r'(?P<option>[^=\s][^=]*)' # very permissive!
+ r'\s*(?P<vi>[=])\s*' # any number of space/tab,
+ # followed by separator
+ # (either : or =), followed
+ # by any # space/tab
+ r'(?P<value>.*)$' # everything up to eol
+ )
+
+ RAWRE = re.compile(
+ r'^[A-Z][A-Za-z]*:\s?' # 'Name: '
+ r'".*";' # '"value ...";' and
+ )
+
+ def _read(self, fp, fpname):
+ """Parse a sectioned setup file.
+
+ From ConfigParser.RawConfigParser
+ """
+ cursect = None # None, or a dictionary
+ curraw = None
+ optname = None
+ lineno = 0
+ e = None # None, or an exception
+ while True:
+ line = fp.readline()
+ if not line:
+ break
+ lineno = lineno + 1
+ # comment or blank line?
+ if line.strip() == '' or line[0] in '#;':
+ continue
+ if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
+ # no leading whitespace
+ continue
+ # continuation line?
+ if line[0].isspace() and cursect is not None and optname:
+ value = line.strip()
+ if value:
+ cursect[optname] = "%s\n%s" % (cursect[optname], value)
+ # a section header or option header?
+ else:
+ # is it a section header?
+ mo = self.SECTCRE.match(line)
+ if mo:
+ sectname = mo.group('header')
+ if sectname in self._sections:
+ cursect = self._sections[sectname]
+ curraw = self._raw[sectname]
+ elif sectname == DEFAULTSECT:
+ cursect = self._defaults
+ else:
+ cursect = {'__name__': sectname}
+ curraw = [] # new
+ self._order.append(sectname) # new
+ self._sections[sectname] = cursect
+ self._raw[sectname] = curraw # new
+ # So sections can't start with a continuation line
+ optname = None
+ # no section header in the file?
+ elif cursect is None:
+ raise MissingSectionHeaderError(fpname, lineno, line)
+ # an option line?
+ else:
+ mo = self.OPTCRE.match(line)
+ if mo:
+ optname, vi, optval = mo.group('option', 'vi', 'value')
+ if vi in ('=', ':') and ';' in optval:
+ # ';' is a comment delimiter only if it follows
+ # a spacing character
+ pos = optval.find(';')
+ if pos != -1 and optval[pos-1].isspace():
+ optval = optval[:pos]
+ optval = optval.strip()
+ # allow empty values
+ if optval == '""':
+ optval = ''
+ optname = self.optionxform(optname.rstrip())
+ cursect[optname] = optval
+ else:
+ mo = self.RAWRE.match(line) # new
+ if mo:
+ # found a InnoSetup raw line
+ curraw.append(line.strip())
+ else:
+ # a non-fatal parsing error occurred. set up the
+ # exception but keep going. the exception will be
+ # raised at the end of the file and will contain a
+ # list of all bogus lines
+ if not e:
+ e = ParsingError(fpname)
+ e.append(lineno, repr(line))
+ # if any parsing errors occurred, raise an exception
+ if e:
+ raise e
+
+
+class InnoSetupCommandMixin:
+ """Mixin class class for a distutils command
+
+ You have call initialize_options() and run() from your class!
+
+ >>> from tempfile import mkstemp
+ >>> tmphdlr, tmpfile = mkstemp()
+
+ >>> test = InnoSetupCommandMixin()
+ >>> test.initialize_options()
+ >>> test.app_name = "Test App"
+ >>> test.innosetup = True
+ >>> test.inno_script = tmpfile
+ >>> test.lib_dir = 'li',
+ >>> test.dist_dir = 'dist'
+ >>> test.windows_exe_files = [r'dist\\test.exe']
+ >>> test.lib_files = [r'dist\\lib1', r'dist\\lib2']
+
+ >>> try:
+ ... test.run()
+ ... finally:
+ ... data = open(tmpfile).read()
+ ... os.unlink(tmpfile)
+
+ #>>> print data
+ """
+ def initialize_options(self):
+ self.app_name = ''
+ self.innosetup = False
+ self.inno_script = None
+ self.inno_version = "1.0"
+ self.inno_templates = None
+ self.inno_interpolation = {}
+ self.inno_sections = {}
+ self.inno_languages = [('nl', 'Dutch'), ('de', 'German'),
+ ('fr', 'French'), ('it', 'Italian'),
+ ('es', 'Spanish')
+ ]
+
+ def run(self):
+ self._createInnoSetup()
+
+ def _createInnoSetup(self):
+ if not self.innosetup:
+ return
+
+ self._inno_script = InnoScript(
+ self.app_name,
+ self.lib_dir,
+ self.dist_dir,
+ self.windows_exe_files,
+ self.lib_files,
+ inno_script = self.inno_script,
+ templates = self.inno_templates,
+ interpolation = self.inno_interpolation,
+ sections = self.inno_sections,
+ languages = self.inno_languages)
+
+ print "*** creating the inno setup script***"
+ self._inno_script.create()
+ print "*** compiling the inno setup script***"
+ try:
+ self._inno_script.compile()
+ except RuntimeError, msg:
+ print "Failed to create installer:\n%s" % msg
+ # Note: By default the final setup.exe will be in an Output subdirectory.
+
+class InnoScript:
+ """Based on py2exe/samples/extending/setup.py
+
+ Requires http://www.jrsoftware.org
+
+ appname - name of the app
+ lib_dir - internal
+ dist_dir - internal
+ windows_exe_files - internal
+ lib_files - internal
+ isscript=None - path to IS script output file
+ templates = None - list of template file names or single file
+ version = "1.0" - version string
+ interpolation - dict with additional %()s interpolation items
+ sections - dict with additional section informations
+ languages - list with languages tuples e.g. [('de', 'German')]
+
+ sections = {'sectioname' :
+ {'key' : 'value',
+ 'RAW' : 'string or list with raw items'
+ }
+ }
+
+ """
+ def __init__(self,
+ appname,
+ lib_dir,
+ dist_dir,
+ windows_exe_files,
+ lib_files,
+ inno_script=None,
+ templates = None,
+ version = "1.0",
+ interpolation = {},
+ sections = {},
+ languages = []
+ ):
+ self.lib_dir = lib_dir
+ self.dist_dir = dist_dir
+ if not self.dist_dir[-1] in "\\/":
+ self.dist_dir += "\\"
+ self.windows_exe_files = [self.chop(p) for p in windows_exe_files]
+ self.lib_files = [self.chop(p) for p in lib_files]
+ if inno_script is None:
+ self.inno_script = os.path.join(dist_dir, appname.replace(' ', '_')+'.iss')
+ else:
+ self.inno_script = inno_script
+ self.fd = open(self.inno_script, "w")
+
+ ip = interpolation.copy()
+ ip['appname'] = appname
+ ip['version'] = version
+ self.interpolation = ip
+
+ self.cfg = ISSConfigParser(ip)
+ if templates:
+ read = self.cfg.read(templates)
+ self.sections = sections
+ self.languages = languages
+
+ def chop(self, pathname):
+ assert pathname.startswith(self.dist_dir)
+ return pathname[len(self.dist_dir):]
+
+ def create(self):
+ """create Inno Script
+ """
+ self.createInnoScript()
+ self.modifyInnoScript()
+ self.writeInnoScript()
+
+ def createInnoScript(self):
+ """Create Inno Script cfg
+ """
+ cfg = self.cfg
+ cfg.add_header("WARNING: This script has been created by py2exe. Changes to this script")
+ cfg.add_header("will be overwritten the next time py2exe is run!\n")
+
+ cfg.add_sectionif("Setup")
+ # Setup
+ cfg.setif("Setup", "AppName", "%(appname)s")
+ cfg.setif("Setup", "AppVerName", "%(appname)s %(version)s")
+ cfg.setif("Setup", "DefaultDirName", "{pf}\%(appname)s")
+ cfg.setif("Setup", "DefaultGroupName", "%(appname)s")
+
+ self._writeLanguagesSect()
+ self._writeWindowsExeFiles()
+ self._writeLibFiles()
+ self._writeIcons()
+ self._writeSections()
+
+ def _writeLanguagesSect(self):
+ cfg = self.cfg
+ if not self.languages:
+ return
+ cfg.add_sectionif('Languages')
+ for key, lang in self.languages:
+ cfg.set_raw("Languages",
+ 'Name: "%s"; MessagesFile: "compiler:Languages\%s.isl"' %
+ (key, lang))
+
+ def _writeWindowsExeFiles(self):
+ cfg = self.cfg
+ cfg.add_sectionif("Files")
+ for path in self.windows_exe_files:
+ cfg.set_raw("Files",
+ r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion'
+ % (path, os.path.dirname(path)) )
+
+ def _writeLibFiles(self):
+ cfg = self.cfg
+ cfg.add_sectionif("Files")
+ for path in self.lib_files:
+ cfg.set_raw("Files",
+ r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion'
+ % (path, os.path.dirname(path)) )
+
+ def _writeIcons(self):
+ cfg = self.cfg
+ cfg.add_sectionif("Icons")
+ for path in self.windows_exe_files:
+ cfg.set_raw("Icons",
+ 'Name: "{group}\\%(appname)s"; Filename: "{app}\\' + path + '"')
+ cfg.set_raw("Icons", r'Name: "{group}\Uninstall %(appname)s"; Filename: "{uninstallexe}"')
+
+ def _writeSections(self):
+ cfg = self.cfg
+ # Additional things in self.sections
+ for section in self.sections:
+ cfg.add_sectionif(section)
+ for key, value in self.sections[section].items():
+ if key == "RAW":
+ cfg.set_raw(section, value)
+ else:
+ cfg.set(section, key, value)
+
+ def modifyInnoScript(self):
+ """Hook
+ """
+ pass
+
+ def writeInnoScript(self):
+ """Write script to disk
+ """
+ self.cfg.write(self.fd)
+ self.fd.close()
+
+ def compile(self):
+ import ctypes
+ res = ctypes.windll.shell32.ShellExecuteA(0, "compile",
+ self.isscript,
+ None,
+ None,
+ 0)
+ if res < 32:
+ raise RuntimeError("ShellExecute failed, error %d" % res)
+
+ def __call__(self):
+ self.create()
+ self.compile()
+
+def test_suite():
+ import unittest
+ from doctest import DocTestSuite
+ return unittest.TestSuite((
+ DocTestSuite(__name__),
+ ))
+
+if __name__ == '__main__':
+ import unittest
+ unittest.main(defaultTest="test_suite")
Modified: pymoul/trunk/distutils_upx.py
===================================================================
--- pymoul/trunk/distutils_upx.py 2007-01-28 23:51:53 UTC (rev 95)
+++ pymoul/trunk/distutils_upx.py 2007-01-29 12:59:51 UTC (rev 96)
@@ -12,9 +12,9 @@
from distutils import log
from stat import ST_SIZE
from fnmatch import fnmatch
-from ConfigParser import RawConfigParser
+from distutils_iss import InnoSetupCommandMixin
-class UpxCommand:
+class UpxCommand(InnoSetupCommandMixin):
"""Upx packer mixin class for distutils
Usage:
@@ -36,6 +36,7 @@
def initialize_options(self):
result = self._otherclass().initialize_options(self)
+ InnoSetupCommandMixin.initialize_options(self)
self.upx = True
self.upx_args = '--no-color'
self.upx_path = 'upx'
@@ -45,10 +46,6 @@
'dylib', # Mac OS X
]
self.upx_ignore = []
-
- self.app_name = ''
- self.innosetup = False
-
return result
def finalize_options(self):
@@ -65,7 +62,7 @@
def run(self, *args, **kwargs):
result = self._otherclass().run(self, *args, **kwargs)
self._upxPack()
- self._createInnoSetup()
+ InnoSetupCommandMixin.run(self)
return result
def _upxPack(self):
@@ -94,13 +91,13 @@
matches = [pat for pat in self.upx_ignore if fnmatch(basename, pat)]
if matches:
continue
-
+
origsize = os.stat(fname)[ST_SIZE]
self._upxPackFile(os.path.normpath(fname))
newsize = os.stat(fname)[ST_SIZE]
ratio = newsize*100 / origsize
packed.append((basename, origsize, newsize, ratio))
-
+
print "\n*** UPX result ***"
for basename, origsize, newsize, ratio in packed:
print " %s packed to %i%%" % (basename, ratio)
@@ -147,186 +144,10 @@
Find next class in MRO that is not based on UpxCommand class
"""
for c in getmro(cls):
- if not issubclass(c, UpxCommand):
+ if not issubclass(c, (UpxCommand, InnoSetupCommandMixin)):
return c
raise ValueError(cls)
- def _createInnoSetup(self):
- if not self.innosetup:
- return
-
- script = InnoScript(self.app_name,
- self.lib_dir,
- self.dist_dir,
- self.windows_exe_files,
- self.lib_files)
- print "*** creating the inno setup script***"
- script.create()
- print "*** compiling the inno setup script***"
- try:
- script.compile()
- except RuntimeError, msg:
- print "Failed to create installer:\n%s" % msg
- # Note: By default the final setup.exe will be in an Output subdirectory.
-
-class InnoScript:
- """Based on py2exe/samples/extending/setup.py
-
- Requires http://www.jrsoftware.org
- """
- def __init__(self,
- name,
- lib_dir,
- dist_dir,
- windows_exe_files = [],
- lib_files = [],
- version = "1.0"):
- self.lib_dir = lib_dir
- self.dist_dir = dist_dir
- # TODO: better name mangling
- self.pathname = os.path.join(dist_dir, name.replace(' ', '_')+'.iss')
- self.cfg = ISSConfigParser()
- if not self.dist_dir[-1] in "\\/":
- self.dist_dir += "\\"
- self.name = name
- self.version = version
- self.windows_exe_files = [self.chop(p) for p in windows_exe_files]
- self.lib_files = [self.chop(p) for p in lib_files]
- self.setup_kwargs = {
- }
- self.languages = ["dutch", "french", "german", "italian", "spanish"]
-
- def chop(self, pathname):
- assert pathname.startswith(self.dist_dir)
- return pathname[len(self.dist_dir):]
-
- def create(self):
- fd = self.file = open(self.pathname, "w")
- cfg = self.cfg
- cfg.add_header("; WARNING: This script has been created by py2exe. Changes to this script")
- cfg.add_header("; will be overwritten the next time py2exe is run!\n")
- cfg.add_section('Setup')
- cfg.add_section('Files')
- cfg.add_section('Icons')
- cfg.add_section('Languages')
- cfg.add_section('Tasks')
- cfg.add_section('Run')
-
- # Setup
- cfg.set("Setup", "AppName", self.name)
- cfg.set("Setup", "AppVerName", "%s %s" % (self.name, self.version))
- cfg.set("Setup", "DefaultDirName", "{pf}\%s" % self.name)
- cfg.set("Setup", "DefaultGroupName", self.name)
- for key, value in self.setup_kwargs.items():
- cfg.set("Setup", key, value)
- # Languages
- cfg.set_raw('Languages',
- 'Name: "english"; MessagesFile: "compiler:Default.isl"')
- for lang in self.languages:
- cfg.set_raw("Languages",
- 'Name: "%s"; MessagesFile: "compiler:Languages/%s.isl"'
- % (lang, lang.capitalize()))
-
- for path in self.windows_exe_files + self.lib_files:
- cfg.set_raw("Files",
- r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion'
- % (path, os.path.dirname(path))
- )
- for path in self.windows_exe_files:
- cfg.set_raw("Icons",
- 'Name: "{group}\%s"; Filename: "{app}\%s"' %
- (self.name, path)
- )
- cfg.set_raw("Icons", 'Name: "{group}\Uninstall %s"; Filename: "{uninstallexe}"' % self.name)
-
- def compile(self):
- try:
- import ctypes
- except ImportError:
- try:
- import win32api
- except ImportError:
- import os
- os.startfile(self.pathname)
- else:
- #print "Ok, using win32api."
- win32api.ShellExecute(0, "compile",
- self.pathname,
- None,
- None,
- 0)
- else:
- #print "Cool, you have ctypes installed."
- res = ctypes.windll.shell32.ShellExecuteA(0, "compile",
- self.pathname,
- None,
- None,
- 0)
- if res < 32:
- raise RuntimeError("ShellExecute failed, error %d" % res)
-
-class ISSConfigParser(RawConfigParser):
- """Config parser for InnoSetupScripts
-
- Supports *only* writing and no parsing!
- """
- def __init__(self, defaults=None):
- RawConfigParser__init__(self, defaults)
- self._raw = {}
- self._header = []
-
- def add_header(self, value):
- """Add a header comment
- """
- self._header.append(value)
-
- def add_section(self, section):
- """Create a new section in the configuration.
- """
- RawConfigParser.add_section(self, section)
- self._raw[section]= []
-
- def set_raw(self, section, raw):
- """Add a raw string to a section
-
- TODO: use NoSectionError
- """
- self._raw[section] = raw
-
- def _read(self, fp, fpname):
- """Read and parse a filename or a list of filenames.
- """
- raise NotImplementedError
-
- def optionxform(self, optionstr):
- return optionstr
-
- def write(self, fp):
- """Write an .ini-format representation of the configuration state."""
- for header in self._headers:
- fp.write("; %s\n" % header.replace('\n', '; \n'))
- if self._defaults:
- fp.write("[%s]\n" % DEFAULTSECT)
- for (key, value) in self._defaults.items():
- fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
- fp.write("\n")
- for section in self._sections:
- fp.write("[%s]\n" % section)
- for (key, value) in self._sections[section].items():
- if key != "__name__":
- fp.write("%s = %s\n" %
- (key, str(value).replace('\n', '\n\t')))
- for raw in self._raw['section']:
- fp.write(str(raw).replace('\n', '\n\t') +'\n')
- fp.write("\n")
-
- def remove_section(self, section):
- """Remove a file section."""
- existed = RawConfigParser.remove_section(self, section)
- if existed:
- del self._raw[section]
- return existed
-
try:
from py2exe.build_exe import py2exe
except ImportError:
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|