Add py2exe installer support.
This commit is contained in:
parent
36c2be760d
commit
9e28ad5939
197
setup.py
197
setup.py
|
@ -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,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue