PyInstaller

There was a question on dabo-users today regarding a spec file for PyInstaller. Lack of time prevents me from rewriting this at the moment. Here are the dependent files which you'll need to go through line-by-line to fix to your specifications. A lot of the complication in my code is due to:

  • Working with multiple platforms using multiple freezing products (py2exe, PyInstaller, cx_freeze, py2app)
  • Having different deployment targets for different modes (multiuser, single-user, and test)
  • Using text-substitution (lots of the strings come from my App object)

Anyway, here are the files I use. I hope it helps!

* buildwin.bat (type 'buildwin' at the command line)

rmdir /s /q dist


python -O setup.py %1


move dist\main*.exe dist\shutter_studio.exe 
del dist\tcl85.dll dist\tk85.dll dist\_tkinter.pyd
del dist\*wx*richtext*
del .\shutter_studio.exe
deltree /s dist\support

iscc setup.iss

* setup.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from setup_util import locales, iconSubDirs, iconDir, getVersionTuple

make_test_package = ("__TEST__" in sys.argv)
if make_test_package:
	del sys.argv[sys.argv.index("__TEST__")]

make_mu_package = ("__MU__" in sys.argv)
if make_mu_package:
	del sys.argv[sys.argv.index("__MU__")]


import os
import glob

if sys.platform.startswith("darwin"):
	from setuptools import setup
	import py2app
elif sys.platform.startswith("linux"):
	from cx_Freeze import setup, Executable

from App import App

# The applications App object contains all the meta info:
app = App(MainFormClass=None, test_version=make_test_package, mu_version=make_mu_package)

_appName = app.getAppInfo("appName")
_appShortName = app.getAppInfo("appShortName")
_appFileStem = _appShortName.lower().replace(" ", "_")
_appVersion = app.getAppInfo("appVersion")
_appDescription = app.getAppInfo("appDescription")
_copyright = app.getAppInfo("copyright")
_authorName = app.getAppInfo("authorName") 
_authorEmail = app.getAppInfo("authorEmail")
_authorURL = app.getAppInfo("authorURL")
_authorPhone = app.getAppInfo("authorPhone")

_appComments = ("This is custom software by %s.\r\n"
		"\r\n"
		"%s\r\n" 
		"%s\r\n" 
		"%s\r\n") % (_authorName, _authorEmail, _authorURL, _authorPhone)


_appIcon = "./resources/icon_green.ico"


if make_test_package:
	_script = "main_TEST.py"
elif make_mu_package:
	_script = "main_MU.py"
else:
	_script = "main.py"


class Target:
	def __init__(self, **kw):
		self.__dict__.update(kw)
		# for the versioninfo resources
		self.version = _appVersion
		self.company_name = _authorName
		self.copyright = _copyright
		self.name = _appName
		self.description = _appDescription
		self.comments = _appComments

		self.script = _script
		self.other_resources = [(24, 1, manifest)]
		if _appIcon is not None:
			self.icon_resources = [(4, _appIcon)]



if sys.platform.startswith("win"):
	_versionTupleStr = str(getVersionTuple(_appVersion))
	vinfo = open("vinfo.txt.spec").read() % locals()
	open("vinfo.txt", "w").write(vinfo)

	spec = open("sbs_studio.spec.txt").read() % locals()
	open("sbs_studio.spec", "w").write(spec)

	os.system("python -O c:\python26\lib\site-packages\pyinstaller\Build.py sbs_studio.spec")
	if not make_mu_package:
		os.system("del dist\*mysql*")

	# Write out the setup.iss file for inno:
	iss = open("setup.iss.txt").read() % locals()
	open("setup.iss", "w").write(iss)
	sys.exit()


data_files=[("db/sqlite", glob.glob("db/sqlite/*.sql")),
		("resources", glob.glob(os.path.join(iconDir, "*.ico"))),
		("resources", glob.glob("resources/*")),
		("reports", glob.glob("reports/*")),
		("messages", [i for i in glob.glob("messages/*") if ("messages%sold" % os.sep) not in i]),
		("db/updates", glob.glob("db/updates/*"))]
data_files.extend(iconSubDirs)
data_files.extend(locales)


if sys.platform.startswith("darwin"):
	options = {"py2app":
	              {"includes": ["App", "__version__", "constants", "db.updates", 
	                            "ui", "biz", "ss_common.lib.floatcanvas",
	                            "encodings", "wx", "wx.lib.calendar", "wx.gizmos"],
	              "optimize": 2,
	              "excludes": ["matplotlib", "Tkconstants","Tkinter","tcl", 
	                          "_imagingtk", "PIL._imagingtk",
	                          "ImageTk", "PIL.ImageTk", "FixTk", "wxPython",
	                          ],
	              "argv_emulation": True,
	              "resources": data_files,
	              "plist": dict(CFBundleGetInfoString=_appVersion,
	                            CFBundleIdentifier="com.sanbenitoshutter.sbs_studio",
	                            LSPrefersPPC=False,
	                            NSHumanReadableCopyright=_copyright
	                           ),
	              "iconfile": "resources/logo_green.icns",
	              }
	          }

	setup(name="SBS Studio",
			app=[_script],
			version=_appVersion,
			description=_appDescription,
			author=_authorName,
			author_email=_authorEmail,
			url=_authorURL,
			options=options,
			#data_files=data_files,
			setup_requires=["py2app"]
	)

elif sys.platform.startswith("linux"):
	include_files = [(i[1], i[0]) for i in data_files]
	### need to unpack the globbed files for cx_Freeze
	unpacked = []
	for source, destination in include_files:
		for source_item in source:
			destination_item = os.path.join(destination, os.path.split(source_item)[-1])
			unpacked.append((source_item, destination_item))
	include_files = unpacked
	options = {"build_exe": {"include_files": include_files,
	                         "includes": ["wx.gizmos", "wx.lib.calendar", 
	                         "numpy.core._internal", "numpy.distutils.unixccompiler",
	                         "distutils.unixccompiler"],
	                         "optimize": 2,
	                         "create_shared_zip": False
	}}
	setup(name="SBS Studio",
			version=_appVersion,
			description=_appDescription,
			executables=[Executable("sbs_studio.py", compress=True,
					appendScriptToExe=True,
					icon=_appIcon)],
			options=options)
  • sbs_studio.spec.txt
    # -*- mode: python -*-
    import sys
    sys.path.insert(0, r"c:\ss")
    from setup_util import localeDir, iconDir
    daboIconTree = Tree(iconDir, "resources", [".svn", "cards"])
    daboLocaleTree = Tree(localeDir, "dabo.locale", [".svn"])
    ssResourceTree = Tree(r"c:\ss\resources", "resources", [".svn"])
    ssReportsTree = Tree(r"c:\ss\reports", "reports", [".svn", "*.py", "*.pyc", "*.pyo"])
    ssDbTree = Tree(r"c:\ss\db\sqlite", "db\sqlite", [".svn"])
    ssDbUpdatesTree = Tree(r"c:\ss\db\updates", "db\updates", [".svn", "*.pyc", "*.pyo", "readme"])
    ssMessagesTree = Tree(r"c:\ss\messages", "messages", [".svn"])
    
    a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), '%(_script)s'],
                 pathex=['C:\\Python26\\Lib\\site-packages\\pyinstaller'])
    pyz = PYZ(a.pure)
    
    exe = EXE(pyz,
              a.scripts + [('O','','OPTION')],
              exclude_binaries=1,
              name="shutter_studio.exe",
              debug=False,
              strip=False,
              upx=True,
              console=False,
              icon='%(_appIcon)s',
              version='./vinfo.txt')
    
    coll = COLLECT( exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   daboIconTree,
                   daboLocaleTree,
                   ssResourceTree,
                   ssReportsTree,
                   ssDbTree,
                   ssDbUpdatesTree,
                   ssMessagesTree,
                   strip=False,
                   upx=True,
                   name='dist')
    
    app = BUNDLE(coll,
                 name=os.path.join('dist', 'sbs_studio.app'))
    
  • vinfo.txt.spec
    VSVersionInfo(
      ffi=FixedFileInfo(
        filevers=%(_versionTupleStr)s,
        prodvers=%(_versionTupleStr)s,
        mask=0x3f,
        flags=0x0,
        OS=0x4,
        fileType=0x1,
        subtype=0x0,
        date=(0, 0)
        ),
      kids=[
        StringFileInfo(
          [
          StringTable(
            '000004b0', 
            [StringStruct('Comments', 'Custom software by Paul McNett (p@ulmcnett.com)'),
            StringStruct('CompanyName', '%(_authorName)s'),
            StringStruct('FileDescription', '%(_appDescription)s'),
            StringStruct('FileVersion', '%(_appVersion)s'),
            StringStruct('LegalCopyright', '%(_copyright)s'),
            StringStruct('ProductName', '%(_appName)s'),
            StringStruct('ProductVersion', '%(_appVersion)s')])
          ]), 
        VarFileInfo([VarStruct('Translation', [0, 1200])])
      ]
    )
    
  • setup_util.py
    import os
    import glob
    import dabo
    import dabo.icons
    
    daboDir = os.path.split(dabo.__file__)[0]
    
    # Find the location of the dabo icons:
    iconDir = os.path.split(dabo.icons.__file__)[0]
    iconSubDirs = []
    def getIconSubDir(arg, dirname, fnames):
    	if ".svn" not in dirname and "cards" not in dirname.lower() and dirname[-1] != "\\":
    		icons = glob.glob(os.path.join(dirname, "*.png"))
    		if icons:
    			subdir = (os.path.join("resources", dirname[len(arg)+1:]), icons)
    			iconSubDirs.append(subdir)
    os.path.walk(iconDir, getIconSubDir, iconDir)
    
    # locales:
    localeDir = "%s%slocale" % (daboDir, os.sep)
    #locales = [("dabo.locale", (os.path.join(daboDir, "locale", "dabo.pot"),))]
    locales = []
    def getLocales(arg, dirname, fnames):
    	if ".svn" not in dirname and dirname[-1] != "\\":
    		#po_files = tuple(glob.glob(os.path.join(dirname, "*.po")))
    		mo_files = tuple(glob.glob(os.path.join(dirname, "*.mo")))
    		if mo_files:
    			subdir = os.path.join("dabo.locale", dirname[len(arg)+1:])
    			locales.append((subdir, mo_files))
    os.path.walk(localeDir, getLocales, localeDir)
    
    
    
    def getVersionTuple(ss_version):
    	l = [int(i) for i in ss_version.split(".")] + [0]
    	return tuple(l)