2
# Copyright (C) 2010 Canonical Ltd
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
"""Repackaging of 'setup.py py2exe'."""
26
def get_setup_info(meta_info, base_packages):
27
"""Collect the information needed to complete a setup.
29
:return: options_list, console_targets, gui_targets, data_files
31
target = py2exe.build_exe.Target(script = "bzr",
33
icon_resources = [(0,'bzr.ico')],
34
name = meta_info['name'],
35
version = bzr_version_str(),
36
description = meta_info['description'],
37
author = meta_info['author'],
38
copyright = "(c) Canonical Ltd, 2005-2010",
39
company_name = "Canonical Ltd.",
40
comments = meta_info['description'],
43
for i in glob.glob('bzrlib\\*.py'):
44
module = i[:-3].replace('\\', '.')
45
if module.endswith('__init__'):
46
module = module[:-len('__init__')]
47
includes.append(module)
49
additional_packages = set()
50
if sys.version.startswith('2.4'):
51
# adding elementtree package
52
additional_packages.add('elementtree')
53
elif sys.version.startswith('2.5'):
54
additional_packages.add('xml.etree')
57
warnings.warn('Unknown Python version.\n'
58
'Please check setup.py script for compatibility.')
60
# Although we currently can't enforce it, we consider it an error for
61
# py2exe to report any files are "missing". Such modules we know aren't
62
# used should be listed here.
63
excludes = """Tkinter psyco ElementPath r_hmac
64
ImaginaryModule cElementTree elementtree.ElementTree
65
Crypto.PublicKey._fastmath
66
medusa medusa.filesys medusa.ftp_server
68
resource validate""".split()
71
# email package from std python library use lazy import,
72
# so we need to explicitly add all package
73
additional_packages.add('email')
74
# And it uses funky mappings to conver to 'Oldname' to 'newname'. As
75
# a result, packages like 'email.Parser' show as missing. Tell py2exe
78
for oldname in getattr(email, '_LOWERNAMES', []):
79
excludes.append("email." + oldname)
80
for oldname in getattr(email, '_MIMENAMES', []):
81
excludes.append("email.MIME" + oldname)
83
# text files for help topis
84
text_topics = glob.glob('bzrlib/help_topics/en/*.txt')
85
topics_files = [('lib/help_topics/en', text_topics)]
89
plugins = None # will be a set after plugin sniffing...
90
for root, dirs, files in os.walk('bzrlib/plugins'):
91
if root == 'bzrlib/plugins':
93
# We ship plugins as normal files on the file-system - however,
94
# the build process can cause *some* of these plugin files to end
95
# up in library.zip. Thus, we saw (eg) "plugins/svn/test" in
96
# library.zip, and then saw import errors related to that as the
97
# rest of the svn plugin wasn't. So we tell py2exe to leave the
98
# plugins out of the .zip file
99
excludes.extend(["bzrlib.plugins." + d for d in dirs])
102
if os.path.splitext(i)[1] not in [".py", ".pyd", ".dll", ".mo"]:
104
if i == '__init__.py' and root == 'bzrlib/plugins':
106
x.append(os.path.join(root, i))
108
target_dir = root[len('bzrlib/'):] # install to 'plugins/...'
109
plugins_files.append((target_dir, x))
110
# find modules for built-in plugins
111
import tools.package_mf
112
mf = tools.package_mf.CustomModuleFinder()
113
mf.run_package('bzrlib/plugins')
114
packs, mods = mf.get_result()
115
additional_packages.update(packs)
116
includes.extend(mods)
118
console_targets = [target,
119
'tools/win32/bzr_postinstall.py',
122
data_files = topics_files + plugins_files
124
if 'qbzr' in plugins:
125
get_qbzr_py2exe_info(includes, excludes, packages, data_files)
128
get_svn_py2exe_info(includes, excludes, packages)
130
if "TBZR" in os.environ:
131
# TORTOISE_OVERLAYS_MSI_WIN32 must be set to the location of the
132
# TortoiseOverlays MSI installer file. It is in the TSVN svn repo and
133
# can be downloaded from (username=guest, blank password):
134
# http://tortoisesvn.tigris.org/svn/tortoisesvn/TortoiseOverlays
135
# look for: version-1.0.4/bin/TortoiseOverlays-1.0.4.11886-win32.msi
136
# Ditto for TORTOISE_OVERLAYS_MSI_X64, pointing at *-x64.msi.
137
for needed in ('TORTOISE_OVERLAYS_MSI_WIN32',
138
'TORTOISE_OVERLAYS_MSI_X64'):
139
url = ('http://guest:@tortoisesvn.tigris.org/svn/tortoisesvn'
141
if not os.path.isfile(os.environ.get(needed, '<nofile>')):
143
"\nPlease set %s to the location of the relevant"
144
"\nTortoiseOverlays .msi installer file."
145
" The installers can be found at"
147
"\ncheck in the version-X.Y.Z/bin/ subdir" % (needed, url))
148
get_tbzr_py2exe_info(includes, excludes, packages, console_targets,
149
gui_targets, data_files)
151
# print this warning to stderr as output is redirected, so it is seen
152
# at build time. Also to stdout so it appears in the log
153
for f in (sys.stderr, sys.stdout):
155
"Skipping TBZR binaries - please set TBZR to a directory to enable"
157
# MSWSOCK.dll is a system-specific library, which py2exe accidentally pulls
159
dll_excludes.extend(["MSWSOCK.dll", "MSVCP60.dll", "powrprof.dll"])
160
options_list = {"py2exe": {"packages": packages + list(additional_packages),
161
"includes": includes,
162
"excludes": excludes,
163
"dll_excludes": dll_excludes,
164
"dist_dir": "win32_bzr.exe",
168
return options_list, console_targets, gui_targets, data_files
171
def bzr_version_str():
172
"""Pick real bzr version."""
175
for i in bzrlib.version_info[:4]:
180
version_number.append(str(i))
181
return '.'.join(version_number)
184
def get_tbzr_py2exe_info(includes, excludes, packages, console_targets,
185
gui_targets, data_files):
186
packages.append('tbzrcommands')
188
# ModuleFinder can't handle runtime changes to __path__, but
189
# win32com uses them. Hook this in so win32com.shell is found.
192
import cPickle as pickle
193
for p in win32com.__path__[1:]:
194
modulefinder.AddPackagePath("win32com", p)
195
for extra in ["win32com.shell"]:
197
m = sys.modules[extra]
198
for p in m.__path__[1:]:
199
modulefinder.AddPackagePath(extra, p)
201
# TBZR points to the TBZR directory
202
tbzr_root = os.environ["TBZR"]
204
# Ensure tbzrlib itself is on sys.path
205
sys.path.append(tbzr_root)
207
packages.append("tbzrlib")
209
# collect up our icons.
211
ico_root = os.path.join(tbzr_root, 'tbzrlib', 'resources')
212
icos = [] # list of (path_root, relative_ico_path)
213
# First always bzr's icon and its in the root of the bzr tree.
214
icos.append(('', 'bzr.ico'))
215
for root, dirs, files in os.walk(ico_root):
216
icos.extend([(ico_root, os.path.join(root, f)[len(ico_root)+1:])
217
for f in files if f.endswith('.ico')])
218
# allocate an icon ID for each file and the full path to the ico
219
icon_resources = [(rid, os.path.join(ico_dir, ico_name))
220
for rid, (ico_dir, ico_name) in enumerate(icos)]
221
# create a string resource with the mapping. Might as well save the
222
# runtime some effort and write a pickle.
223
# Runtime expects unicode objects with forward-slash seps.
224
fse = sys.getfilesystemencoding()
225
map_items = [(f.replace('\\', '/').decode(fse), rid)
226
for rid, (_, f) in enumerate(icos)]
227
ico_map = dict(map_items)
228
# Create a new resource type of 'ICON_MAP', and use ID=1
229
other_resources = [ ("ICON_MAP", 1, pickle.dumps(ico_map))]
231
excludes.extend("""pywin pywin.dialogs pywin.dialogs.list
232
win32ui crawler.Crawler""".split())
234
# tbzrcache executables - a "console" version for debugging and a
235
# GUI version that is generally used.
237
script = os.path.join(tbzr_root, "scripts", "tbzrcache.py"),
238
icon_resources = icon_resources,
239
other_resources = other_resources,
241
console_targets.append(tbzrcache)
243
# Make a windows version which is the same except for the base name.
244
tbzrcachew = tbzrcache.copy()
245
tbzrcachew["dest_base"]="tbzrcachew"
246
gui_targets.append(tbzrcachew)
248
# ditto for the tbzrcommand tool
250
script = os.path.join(tbzr_root, "scripts", "tbzrcommand.py"),
251
icon_resources = [(0,'bzr.ico')],
253
console_targets.append(tbzrcommand)
254
tbzrcommandw = tbzrcommand.copy()
255
tbzrcommandw["dest_base"]="tbzrcommandw"
256
gui_targets.append(tbzrcommandw)
258
# A utility to see python output from both C++ and Python based shell
260
tracer = dict(script=os.path.join(tbzr_root, "scripts", "tbzrtrace.py"))
261
console_targets.append(tracer)
263
# The C++ implemented shell extensions.
264
dist_dir = os.path.join(tbzr_root, "shellext", "build")
265
data_files.append(('', [os.path.join(dist_dir, 'tbzrshellext_x86.dll')]))
266
data_files.append(('', [os.path.join(dist_dir, 'tbzrshellext_x64.dll')]))
269
def get_qbzr_py2exe_info(includes, excludes, packages, data_files):
270
# PyQt4 itself still escapes the plugin detection code for some reason...
271
packages.append('PyQt4')
272
excludes.append('PyQt4.elementtree.ElementTree')
273
excludes.append('PyQt4.uic.port_v3')
274
includes.append('sip') # extension module required for Qt.
275
packages.append('pygments') # colorizer for qbzr
276
packages.append('docutils') # html formatting
277
includes.append('win32event') # for qsubprocess stuff
278
# but we can avoid many Qt4 Dlls.
280
"""QtAssistantClient4.dll QtCLucene4.dll QtDesigner4.dll
281
QtHelp4.dll QtNetwork4.dll QtOpenGL4.dll QtScript4.dll
282
QtSql4.dll QtTest4.dll QtWebKit4.dll QtXml4.dll
283
qscintilla2.dll""".split())
284
# the qt binaries might not be on PATH...
285
# They seem to install to a place like C:\Python25\PyQt4\*
286
# Which is not the same as C:\Python25\Lib\site-packages\PyQt4
287
pyqt_dir = os.path.join(sys.prefix, "PyQt4")
288
pyqt_bin_dir = os.path.join(pyqt_dir, "bin")
289
if os.path.isdir(pyqt_bin_dir):
290
path = os.environ.get("PATH", "")
291
if pyqt_bin_dir.lower() not in [p.lower() for p in path.split(os.pathsep)]:
292
os.environ["PATH"] = path + os.pathsep + pyqt_bin_dir
293
# also add all imageformat plugins to distribution
294
# We will look in 2 places, dirname(PyQt4.__file__) and pyqt_dir
295
base_dirs_to_check = []
296
if os.path.isdir(pyqt_dir):
297
base_dirs_to_check.append(pyqt_dir)
303
pyqt4_base_dir = os.path.dirname(PyQt4.__file__)
304
if pyqt4_base_dir != pyqt_dir:
305
base_dirs_to_check.append(pyqt4_base_dir)
306
if not base_dirs_to_check:
307
log.warn("Can't find PyQt4 installation -> not including imageformat"
311
for base_dir in base_dirs_to_check:
312
plug_dir = os.path.join(base_dir, 'plugins', 'imageformats')
313
if os.path.isdir(plug_dir):
314
for fname in os.listdir(plug_dir):
315
# Include plugin dlls, but not debugging dlls
316
fullpath = os.path.join(plug_dir, fname)
317
if fname.endswith('.dll') and not fname.endswith('d4.dll'):
318
files.append(fullpath)
320
data_files.append(('imageformats', files))
322
log.warn('PyQt4 was found, but we could not find any imageformat'
323
' plugins. Are you sure your configuration is correct?')
326
def get_svn_py2exe_info(includes, excludes, packages):
327
packages.append('subvertpy')
330
def get_bzrlib_packages():
331
"""Recurse through the bzrlib directory, and extract the package names"""
334
base_path = os.path.dirname(os.path.abspath(bzrlib.__file__))
335
for root, dirs, files in os.walk(base_path):
336
if '__init__.py' in files:
337
assert root.startswith(base_path)
338
# Get just the path below bzrlib
339
package_path = root[len(base_path):]
340
# Remove leading and trailing slashes
341
package_path = package_path.strip('\\/')
343
package_name = 'bzrlib'
345
package_name = ('bzrlib.' +
346
package_path.replace('/', '.').replace('\\', '.'))
347
packages.append(package_name)
348
return sorted(packages)
351
# An override to install_data used only by py2exe builds, which arranges
352
# to byte-compile any .py files in data_files (eg, our plugins)
353
# Necessary as we can't rely on the user having the relevant permissions
354
# to the "Program Files" directory to generate them on the fly.
355
class install_data_with_bytecompile(install_data):
357
from distutils.util import byte_compile
359
install_data.run(self)
360
py2exe = self.distribution.get_command_obj('py2exe', False)
361
optimize = py2exe.optimize
362
compile_names = [f for f in self.outfiles if f.endswith('.py')]
363
byte_compile(compile_names, optimize=optimize,
364
force=self.force, prefix=self.install_dir, dry_run=self.dry_run)
369
self.outfiles.extend([f + suffix for f in compile_names])
373
from distutils.core import setup
375
# Copy-and-paste of some settings from bzr's setup.py
378
'author': 'Canonical Ltd',
379
'description': 'Friendly distributed version control system',
381
base_packages = get_bzrlib_packages()
382
base_packages.remove('bzrlib')
383
base_packages = [i for i in base_packages
384
if not i.startswith('bzrlib.plugins')]
385
options_list, console_targets, gui_targets, data_files = get_setup_info(
386
META_INFO, base_packages)
387
setup(options=options_list, console=console_targets, windows=gui_targets,
388
zipfile='lib/library.zip', data_files=data_files,
389
cmdclass={'install_data': install_data_with_bytecompile})
392
if __name__ == "__main__":