Add py2exe installer support.

This commit is contained in:
Bastian Kleineidam 2012-05-18 20:49:43 +02:00
parent 36c2be760d
commit 9e28ad5939
1 changed files with 197 additions and 0 deletions

197
setup.py
View File

@ -22,19 +22,209 @@ import sys
if not hasattr(sys, "version_info") or sys.version_info < (2, 4, 0, "final", 0): if not hasattr(sys, "version_info") or sys.version_info < (2, 4, 0, "final", 0):
raise SystemExit("This program requires Python 2.4 or later.") raise SystemExit("This program requires Python 2.4 or later.")
import os import os
import shutil
import glob
import subprocess
from distutils.core import setup from distutils.core import setup
try:
# py2exe monkey-patches the distutils.core.Distribution class
# So we need to import it before importing the Distribution class
import py2exe
has_py2exe = True
except ImportError:
# py2exe is not installed
has_py2exe = False
from distutils.core import Distribution
from distutils import util
AppName = "patool" AppName = "patool"
AppVersion = "0.17" AppVersion = "0.17"
MyName = "Bastian Kleineidam" MyName = "Bastian Kleineidam"
MyEmail = "calvin@users.sourceforge.net" MyEmail = "calvin@users.sourceforge.net"
# basic excludes for py2exe and py2app
# py2exe options for windows .exe packaging
py2exe_options = dict(
packages=["encodings"],
excludes=['doctest', 'unittest', 'optcomplete', 'Tkinter'],
# silence py2exe error about not finding msvcp90.dll
dll_excludes=['MSVCP90.dll'],
compressed=1,
optimize=2,
)
# Microsoft Visual C++ runtime version (tested with Python 2.7.2)
MSVCP90Version = '9.0.21022.8'
MSVCP90Token = '1fc8b3b9a1e18e3b'
data_files = [] data_files = []
if os.name == 'nt': if os.name == 'nt':
data_files.append(('share', ['doc/patool.txt'])) data_files.append(('share', ['doc/patool.txt']))
else: else:
data_files.append(('share/man/man1', ['doc/patool.1'])) data_files.append(('share/man/man1', ['doc/patool.1']))
def get_nt_platform_vars ():
"""Return program file path and architecture for NT systems."""
platform = util.get_platform()
if platform == "win-amd64":
# the Visual C++ runtime files are installed in the x86 directory
progvar = "%ProgramFiles(x86)%"
architecture = "amd64"
elif platform == "win32":
progvar = "%ProgramFiles%"
architecture = "x86"
else:
raise ValueError("Unsupported platform %r" % platform)
return os.path.expandvars(progvar), architecture
def add_msvc_files (files):
"""Add needed MSVC++ runtime files. Only Version 9.0.21022.8 is tested
and can be downloaded here:
http://www.microsoft.com/en-us/download/details.aspx?id=29
"""
prog_dir, architecture = get_nt_platform_vars()
dirname = "Microsoft.VC90.CRT"
version = "%s_%s_x-ww_d08d0375" % (MSVCP90Token, MSVCP90Version)
args = (architecture, dirname, version)
path = r'C:\Windows\WinSxS\%s_%s_%s\*.*' % args
files.append((dirname, glob.glob(path)))
# Copy the manifest file into the build directory and rename it
# because it must have the same name as the directory.
path = r'C:\Windows\WinSxS\Manifests\%s_%s_%s.manifest' % args
target = os.path.join(os.getcwd(), 'build', '%s.manifest' % dirname)
shutil.copy(path, target)
files.append((dirname, [target]))
if 'py2exe' in sys.argv[1:]:
if not has_py2exe:
raise SystemExit("py2exe module could not be imported")
add_msvc_files(data_files)
class MyDistribution (Distribution, object):
"""Custom distribution class generating config file."""
def __init__ (self, attrs):
"""Set console and windows scripts."""
super(MyDistribution, self).__init__(attrs)
self.console = ['patool']
class InnoScript:
"""Class to generate INNO script."""
def __init__(self, lib_dir, dist_dir, windows_exe_files=[],
console_exe_files=[], service_exe_files=[],
comserver_files=[], lib_files=[]):
"""Store INNO script infos."""
self.lib_dir = lib_dir
self.dist_dir = dist_dir
if not self.dist_dir[-1] in "\\/":
self.dist_dir += "\\"
self.name = AppName
self.version = AppVersion
self.windows_exe_files = [self.chop(p) for p in windows_exe_files]
self.console_exe_files = [self.chop(p) for p in console_exe_files]
self.service_exe_files = [self.chop(p) for p in service_exe_files]
self.comserver_files = [self.chop(p) for p in comserver_files]
self.lib_files = [self.chop(p) for p in lib_files]
self.icon = os.path.abspath(r'doc\icon\favicon.ico')
def chop(self, pathname):
"""Remove distribution directory from path name."""
assert pathname.startswith(self.dist_dir)
return pathname[len(self.dist_dir):]
def create(self, pathname=r"dist\omt.iss"):
"""Create Inno script."""
self.pathname = pathname
self.distfilebase = "%s-%s" % (self.name, self.version)
self.distfile = self.distfilebase + ".exe"
with open(self.pathname, "w") as fd:
self.write_inno_script(fd)
def write_inno_script (self, fd):
"""Write Inno script contents."""
print >> fd, "; WARNING: This script has been created by py2exe. Changes to this script"
print >> fd, "; will be overwritten the next time py2exe is run!"
print >> fd, "[Setup]"
print >> fd, "AppName=%s" % self.name
print >> fd, "AppVerName=%s %s" % (self.name, self.version)
print >> fd, r"DefaultDirName={pf}\%s" % self.name
print >> fd, "DefaultGroupName=%s" % self.name
print >> fd, "OutputBaseFilename=%s" % self.distfilebase
print >> fd, "OutputDir=.."
print >> fd, "SetupIconFile=%s" % self.icon
print >> fd
# List of source files
files = self.windows_exe_files + \
self.console_exe_files + \
self.service_exe_files + \
self.comserver_files + \
self.lib_files
print >> fd, '[Files]'
for path in files:
print >> fd, r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion' % (path, os.path.dirname(path))
# Set icon filename
print >> fd, '[Icons]'
for path in self.windows_exe_files:
print >> fd, r'Name: "{group}\%s"; Filename: "{app}\%s"' % \
(self.name, path)
print >> fd, r'Name: "{group}\Uninstall %s"; Filename: "{uninstallexe}"' % self.name
print >> fd
# Uninstall optional log files
print >> fd, '[UninstallDelete]'
print >> fd, r'Type: files; Name: "{pf}\%s\patool*.exe.log"' % self.name
print >> fd
def compile (self):
"""Compile Inno script with iscc.exe."""
progpath = get_nt_platform_vars()[0]
cmd = r'%s\Inno Setup 5\iscc.exe' % progpath
subprocess.check_call([cmd, self.pathname])
def sign (self):
"""Sign InnoSetup installer with local self-signed certificate."""
pfxfile = r'C:\certificate.pfx'
if os.path.isfile(pfxfile):
cmd = ['signtool.exe', 'sign', '/f', pfxfile, self.distfile]
subprocess.check_call(cmd)
else:
print "No signed installer: certificate %s not found." % pfxfile
try:
from py2exe.build_exe import py2exe as py2exe_build
class MyPy2exe (py2exe_build):
"""First builds the exe file(s), then creates a Windows installer.
Needs InnoSetup to be installed."""
def run (self):
"""Generate py2exe installer."""
# First, let py2exe do it's work.
py2exe_build.run(self)
print "*** preparing the inno setup script ***"
lib_dir = self.lib_dir
dist_dir = self.dist_dir
# create the Installer, using the files py2exe has created.
script = InnoScript(lib_dir, dist_dir, self.windows_exe_files,
self.console_exe_files, self.service_exe_files,
self.comserver_files, self.lib_files)
print "*** creating the inno setup script ***"
script.create()
print "*** compiling the inno setup script ***"
script.compile()
script.sign()
except ImportError:
class MyPy2exe:
"""Dummy py2exe class."""
pass
setup ( setup (
name = AppName, name = AppName,
version = AppVersion, version = AppVersion,
@ -78,4 +268,11 @@ installed.
'Programming Language :: Python', 'Programming Language :: Python',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
], ],
distclass = MyDistribution,
cmdclass = {
'py2exe': MyPy2exe,
},
options = {
"py2exe": py2exe_options,
},
) )