1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
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 St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
39
from glob import glob, iglob
40
from xml.sax.saxutils import escape
41
from distutils import sysconfig
42
from distutils.core import Command
43
import distutils.command.install_data
44
from distutils.ccompiler import new_compiler
45
from distutils import file_util, dir_util
48
###############################################################################
49
## Paths and configuration ##
50
###############################################################################
52
# The location of the NSIS compiler
53
NSIS_PATH = 'C:\\Program Files\\NSIS\\makensis.exe'
55
# This is the version of the binary kit to use
56
BINARY_KIT_VERSION = open("binary_kit_version").read().strip()
58
# If you're using the prebuilt DTV Dependencies Binary Kit, just set
59
# the path to it here, and ignore everything after this point. In
60
# fact, if you unpacked or checked out the binary kit in the same
61
# directory as DTV itself, the default value here will work.
63
# Otherwise, if you build the dependencies yourself instead of using
64
# the Binary Kit, ignore this setting and change all of the settings
66
BINARY_KIT_ROOT = "miro-binary-kit-win-%s" % BINARY_KIT_VERSION
68
if not os.path.exists or not os.path.isdir(BINARY_KIT_ROOT):
69
print "Binary kit %s is missing. Run 'setup_binarykit.sh'." % BINARY_KIT_ROOT
72
ZLIB_INCLUDE_PATH = os.path.join(BINARY_KIT_ROOT, 'zlib', 'include')
73
ZLIB_LIB_PATH = os.path.join(BINARY_KIT_ROOT, 'zlib', 'lib')
74
ZLIB_RUNTIME_LIBRARY_PATH = os.path.join(BINARY_KIT_ROOT, 'zlib')
76
OPENSSL_INCLUDE_PATH = os.path.join(BINARY_KIT_ROOT, 'openssl', 'include')
77
OPENSSL_LIB_PATH = os.path.join(BINARY_KIT_ROOT, 'openssl', 'lib')
78
OPENSSL_LIBRARIES = ['ssleay32', 'libeay32']
80
# GTK_ROOT_PATH = os.path.join(BINARY_KIT_ROOT, 'gtk+-bundle_2.20.0-20100406_win32')
81
GTK_ROOT_PATH = os.path.join(BINARY_KIT_ROOT, 'gtk+-bundle_2.16.6-20091215_win32')
82
GTK_INCLUDE_PATH = os.path.join(GTK_ROOT_PATH, 'include')
83
GTK_LIB_PATH = os.path.join(GTK_ROOT_PATH, 'lib')
84
GTK_BIN_PATH = os.path.join(GTK_ROOT_PATH, 'bin')
86
os.path.join(GTK_INCLUDE_PATH, 'atk-1.0'),
87
os.path.join(GTK_INCLUDE_PATH, 'gtk-2.0'),
88
os.path.join(GTK_INCLUDE_PATH, 'glib-2.0'),
89
os.path.join(GTK_INCLUDE_PATH, 'pango-1.0'),
90
os.path.join(GTK_INCLUDE_PATH, 'cairo'),
91
os.path.join(GTK_LIB_PATH, 'glib-2.0', 'include'),
92
os.path.join(GTK_LIB_PATH, 'gtk-2.0', 'include'),
95
PYGOBJECT_INCLUDE_DIR = os.path.join(BINARY_KIT_ROOT, 'pygobject')
97
# path to the Mozilla "xulrunner-sdk" distribution.
98
XULRUNNER_SDK_PATH = os.path.join(BINARY_KIT_ROOT, 'xulrunner-sdk')
99
XULRUNNER_SDK_BIN_PATH = os.path.join(XULRUNNER_SDK_PATH, 'bin')
101
VLC_PATH = os.path.join(BINARY_KIT_ROOT, 'libvlc')
102
LIBTORRENT_PATH = os.path.join(BINARY_KIT_ROOT, 'libtorrent')
104
FFMPEG_PATH = os.path.join(
105
BINARY_KIT_ROOT, 'ffmpeg', 'ffmpeg-r25766-swscale-r32562-mingw32-static')
106
FFMPEG2THEORA_PATH = os.path.join(BINARY_KIT_ROOT, 'ffmpeg2theora')
108
VCREDIST90_PATH = os.path.join(BINARY_KIT_ROOT, 'vc90redist')
110
def find_data_files(dest_path_base, source_path):
112
for path, dirs, files in os.walk(source_path):
113
if not path.startswith(source_path):
114
raise AssertionError()
115
dest_path = path.replace(source_path, dest_path_base)
116
source_files = [os.path.join(path, f) for f in files]
117
retval.append((dest_path, source_files))
122
# Name of python binary, so we can build the download daemon in
123
# another process. (Can we get this from Python itself?)
124
PYTHON_BINARY = "python"
126
###############################################################################
127
## End of configuration. No user-servicable parts inside ##
128
###############################################################################
130
from distutils.core import setup
131
from distutils.extension import Extension
132
from distutils.core import Command
133
from distutils import log
135
import py2exe.build_exe
139
from Pyrex.Distutils import build_ext
141
# The name of this platform.
144
# Find the top of the source tree and set search path
145
root_dir = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), '..')
146
root_dir = os.path.normpath(os.path.abspath(root_dir))
147
platform_dir = os.path.join(root_dir, 'windows')
148
platform_package_dir = os.path.join(platform_dir, 'plat')
149
widgets_dir = os.path.join(platform_package_dir, 'frontends', 'widgets')
150
portable_dir = os.path.join(root_dir, 'lib')
151
portable_widgets_dir = os.path.join(portable_dir, 'frontends', 'widgets')
152
portable_xpcom_dir = os.path.join(portable_widgets_dir, 'gtk', 'xpcom')
153
test_dir = os.path.join(root_dir, 'resources')
154
resources_dir = os.path.join(root_dir, 'resources')
156
sys.path.insert(0, root_dir)
157
# when we install the portable modules, they will be in the miro
158
# package, but at this point, they are in a package named "lib",
161
sys.modules['miro'] = lib
163
from miro import util
165
# put the libtorrent extension on the path, so py2exe can find the
167
sys.path.insert(0, LIBTORRENT_PATH)
171
pygtkhacks_ext = Extension(
172
"miro.frontends.widgets.gtk.pygtkhacks",
174
os.path.join(portable_widgets_dir, 'gtk', 'pygtkhacks.pyx'),
176
include_dirs=GTK_INCLUDE_DIRS + [PYGOBJECT_INCLUDE_DIR],
177
library_dirs=[GTK_LIB_PATH],
183
xulrunnerbrowser_ext_dir = os.path.join(widgets_dir, 'XULRunnerBrowser')
184
xulrunnerbrowser_ext = Extension(
185
"miro.plat.frontends.widgets.xulrunnerbrowser",
187
os.path.join(XULRUNNER_SDK_PATH, 'sdk', 'include'),
188
os.path.join(XULRUNNER_SDK_PATH, 'include'),
189
os.path.join(XULRUNNER_SDK_PATH, 'include', 'xpcom'),
191
] + GTK_INCLUDE_DIRS,
195
("PCF_USING_XULRUNNER19", 1),
201
os.path.join(XULRUNNER_SDK_PATH, 'lib'),
213
os.path.join(xulrunnerbrowser_ext_dir, 'xulrunnerbrowser.pyx'),
214
os.path.join(portable_xpcom_dir, 'HttpObserver.cc'),
215
os.path.join(xulrunnerbrowser_ext_dir, 'MiroBrowserEmbed.cpp'),
216
os.path.join(xulrunnerbrowser_ext_dir, 'MiroWindowCreator.cpp'),
217
os.path.join(xulrunnerbrowser_ext_dir, 'FixFocus.cpp'),
218
os.path.join(xulrunnerbrowser_ext_dir, 'Init.cpp'),
222
# Setting the path here allows py2exe to find the DLLS
223
os.environ['PATH'] = ';'.join([
224
OPENSSL_LIB_PATH, ZLIB_RUNTIME_LIBRARY_PATH,
225
LIBTORRENT_PATH, GTK_BIN_PATH, os.environ['PATH']])
227
# Private extension modules to build.
230
xulrunnerbrowser_ext,
233
def fill_template(templatepath, outpath, **vars):
234
s = open(templatepath, 'rt').read()
235
s = string.Template(s).safe_substitute(**vars)
236
f = open(outpath, "wt")
242
data_files.extend(find_data_files('xulrunner', XULRUNNER_SDK_BIN_PATH))
244
image_loader_path = os.path.join('lib', 'gtk-2.0', '2.10.0', 'loaders')
245
theme_engine_path = os.path.join('lib', 'gtk-2.0', '2.10.0', 'engines')
246
theme_path = os.path.join('share', 'themes')
247
for path in (image_loader_path, theme_engine_path, theme_path):
248
src_path = os.path.join(GTK_ROOT_PATH, path)
249
data_files.extend(find_data_files(path, src_path))
251
data_files.append(('', iglob(os.path.join(GTK_BIN_PATH, '*.dll'))))
252
data_files.extend(find_data_files(
253
'vlc-plugins', os.path.join(VLC_PATH, 'vlc-plugins')))
254
data_files.append(('', [os.path.join(VLC_PATH, 'libvlc.dll')]))
255
data_files.append(('', [os.path.join(VLC_PATH, 'libvlccore.dll')]))
256
data_files.append(('', [os.path.join(LIBTORRENT_PATH, 'libtorrent.pyd')]))
257
data_files.append(('', [
258
os.path.join(FFMPEG_PATH, 'bin', 'ffmpeg.exe'),
259
os.path.join(FFMPEG2THEORA_PATH, 'bin', 'ffmpeg2theora.exe')]))
260
data_files.append(('', glob(os.path.join(FFMPEG_PATH, 'presets', '*.ffpreset'))))
261
data_files.extend(find_data_files('Microsoft.VC90.CRT',
262
os.path.join(VCREDIST90_PATH, 'Microsoft.VC90.CRT')))
264
# handle the resources subdirectories.
265
for dir in ('searchengines', 'images', 'conversions'):
266
dest_dir = os.path.join('resources', dir)
267
source_dir = os.path.join(resources_dir, dir)
268
data_files.extend(find_data_files(dest_dir, source_dir))
270
data_files.append(('resources', [os.path.join(root_dir, 'ADOPTERS')]))
271
data_files.append(('resources', [os.path.join(root_dir, 'CREDITS')]))
273
locale_temp_dir = os.path.join(os.path.dirname(__file__), "build", "locale")
275
def copy_locale_files():
276
print "*** copying locale files ***"
277
# handle locale files
279
for source in glob(os.path.join(resources_dir, "locale", "*.mo")):
280
lang = os.path.basename(source)[:-3]
281
dest = os.path.join(locale_temp_dir, lang, "LC_MESSAGES", "miro.mo")
282
locale_files.append((source, dest))
284
dir_util.create_tree(os.path.dirname(__file__),
285
[dst for src, dst in locale_files])
287
for source, dest in locale_files:
288
file_util.copy_file(source, dest, update=True, verbose=True)
290
# FIXME - this should be done inside a build command
292
data_files.extend(find_data_files(os.path.join("resources", "locale"),
295
app_config = os.path.join(resources_dir, 'app.config.template')
296
template_vars = util.read_simple_config_file(app_config)
298
# pixmap for the about dialog
299
icon_path = os.path.join("icons", "hicolor", "128x128", "apps")
300
data_files.append((os.path.join("resources", icon_path),
301
[os.path.join(platform_dir, icon_path, "miro.png")]))
303
###########################################################################
305
#### Our specialized install_data command ####
306
class install_data(distutils.command.install_data.install_data):
307
"""install_data extends to default implementation so that it
308
automatically installs app.config from app.config.template.
311
def install_app_config(self):
312
template = os.path.join(resources_dir, 'app.config.template')
313
dest = os.path.join(self.install_dir, 'resources', 'app.config')
314
revision = util.query_revision()
317
revisionurl = "unknown"
318
revisionnum = "unknown"
320
revisionurl = revision[0]
321
revisionnum = revision[1]
322
revision = "%s - %s" % revision
324
print "Using %s" % revisionnum
326
self.mkpath(os.path.dirname(dest))
327
# We don't use the dist utils copy_file() because it only
328
# copies the file if the timestamp is newer
329
fill_template(template, dest,
330
APP_REVISION=revision,
331
APP_REVISION_NUM=revisionnum,
332
APP_REVISION_URL=revisionurl,
333
APP_PLATFORM='windows',
334
BUILD_MACHINE="%s@%s" % (os.environ['username'],
335
socket.gethostname()),
336
BUILD_TIME=str(time.time()))
337
self.outfiles.append(dest)
339
def install_gdk_pixbuf_loaders(self):
340
basename = os.path.join('etc', 'gtk-2.0', 'gdk-pixbuf.loaders')
341
source = os.path.join(GTK_ROOT_PATH, basename)
342
dest = os.path.join(self.install_dir, basename)
343
contents = open(source).read()
344
# Not sure why they have paths like this in the file, but we
345
# need to change them.
346
contents = contents.replace(
347
"c:/devel/target/9c384abfa28a3e070eb60fc2972f823b/", "")
348
self.mkpath(os.path.dirname(dest))
349
open(dest, 'wt').write(contents)
350
self.outfiles.append(dest)
353
distutils.command.install_data.install_data.run(self)
354
self.install_app_config()
355
self.install_gdk_pixbuf_loaders()
357
# We want to make sure we include msvcp90.dll in the dist directory.
359
# http://www.py2exe.org/index.cgi/OverridingCriteraForIncludingDlls
363
origIsSystemDLL = py2exe.build_exe.isSystemDLL
364
def isSystemDLL(pathname):
365
if os.path.basename(pathname).lower() in DLLS_TO_INCLUDE:
368
return origIsSystemDLL(pathname)
369
py2exe.build_exe.isSystemDLL = isSystemDLL
371
class bdist_miro(Command):
372
description = "Build Miro"
376
def initialize_options(self):
379
def finalize_options(self):
383
self.run_command('py2exe')
387
dist_dir = self.get_finalized_command('py2exe').dist_dir
388
shortappname = template_vars["shortAppName"]
389
self.copy_file("Miro.ico",
390
os.path.join(dist_dir, "%s.ico" % shortappname))
392
class bdist_test(Command):
393
description = "Builds Miro with unit tests"
397
def initialize_options(self):
400
def finalize_options(self):
404
self.run_command('bdist_miro')
405
self.copy_test_data()
407
def copy_test_data(self):
408
# copy test data over
409
dist_dir = self.get_finalized_command('py2exe').dist_dir
411
self.copy_tree(os.path.join(resources_dir, 'testdata'),
412
os.path.join(dist_dir, 'resources', 'testdata'))
414
class runmiro(Command):
415
description = "build Miro and start it up"
419
def initialize_options(self):
422
def finalize_options(self):
426
self.run_command('bdist_miro')
428
os.chdir(self.get_finalized_command('py2exe').dist_dir)
429
os.system("%s" % template_vars['shortAppName'])
432
class bdist_nsis(Command):
433
description = "create Miro installer using NSIS"
436
('generic', None, 'Build a generic installer instead of the Miro-branded installer.'),
437
('nozugo', None, 'Do not include the silent Zugo toolbar installer.'),
438
('mozilla', None, 'Do not show the toolbar option to international users.'),
439
('install-icon=', None, 'ICO file to use for the installer.'),
440
('install-image=', None, 'BMP file to use for the welcome/finish pages.')
443
def initialize_options(self):
447
self.install_icon = None
448
self.install_image = None
450
def finalize_options(self):
451
if self.generic and (self.install_icon or self.install_icon):
452
raise AssertionError("cannot specify install images with "
455
self.install_icon = 'miro-installer-generic.ico'
456
self.install_image = 'miro-install-generic.bmp'
457
if self.install_icon is None:
458
self.install_icon = 'miro-installer.ico'
459
if self.install_image is None:
460
self.install_image = 'miro-install-image.bmp'
463
self.run_command('bdist_miro')
464
self.dist_dir = self.get_finalized_command('py2exe').dist_dir
466
log.info("building installer")
468
self.copy_file(os.path.join(platform_dir, 'Miro.nsi'), self.dist_dir)
470
self.copy_file(os.path.join(platform_dir, 'zugo-silent.exe'), self.dist_dir)
471
self.copy_file(self.install_icon, self.dist_dir)
472
self.copy_file(self.install_image, self.dist_dir)
475
for our_name, nsis_name in [('appVersion', 'CONFIG_VERSION'),
476
('projectURL', 'CONFIG_PROJECT_URL'),
477
('shortAppName', 'CONFIG_SHORT_APP_NAME'),
478
('longAppName', 'CONFIG_LONG_APP_NAME'),
479
('publisher', 'CONFIG_PUBLISHER')]:
480
nsis_vars[nsis_name] = template_vars[our_name]
482
nsis_vars['CONFIG_EXECUTABLE'] = "%s.exe" % template_vars['shortAppName']
483
nsis_vars['CONFIG_DOWNLOADER_EXECUTABLE'] = "%s_Downloader.exe" % \
484
template_vars['shortAppName']
485
nsis_vars['CONFIG_MOVIE_DATA_EXECUTABLE'] = "%s_MovieData.exe" % \
486
template_vars['shortAppName']
487
nsis_vars['CONFIG_ICON'] = "%s.ico" % template_vars['shortAppName']
488
nsis_vars['CONFIG_PROG_ID'] = template_vars['longAppName'].replace(" ", ".") + ".1"
489
nsis_vars['MIRO_INSTALL_ICON'] = self.install_icon
490
nsis_vars['MIRO_INSTALL_IMAGE'] = self.install_image
491
nsis_vars['CONFIG_BINARY_KIT'] = BINARY_KIT_ROOT
493
nsis_vars['MIROBAR_EXE'] = 'zugo-silent.exe'
495
nsis_vars['GENERIC_INSTALLER'] = '1'
497
nsis_vars['MOZILLA_INSTALLER'] = '1'
499
output_file = '%s-%s'
500
# One stage installer
502
output_file = "%s-generic" % output_file
504
output_file = "%s-nozugo" % output_file
506
output_file = '%s-mozilla' % output_file
508
output_file = (output_file %
509
(template_vars['shortAppName'], template_vars['appVersion']))
510
nsis_vars['CONFIG_OUTPUT_FILE'] = '%s.exe' % output_file
511
nsis_vars['CONFIG_TWOSTAGE'] = "No"
513
nsis_args = ["/D%s=%s" % (k, v) for (k, v) in nsis_vars.iteritems()]
514
nsis_args.append(os.path.join(self.dist_dir, "Miro.nsi"))
516
if os.access(output_file, os.F_OK):
517
os.remove(output_file)
518
if subprocess.call([NSIS_PATH] + nsis_args) != 0:
519
print "ERROR creating the 1 stage installer, quitting"
522
# Two stage installer
524
output_file = '%s-%s-generic-twostage.exe'
526
output_file = "%s-%s-twostage.exe"
527
output_file = (output_file %
528
(template_vars['shortAppName'], template_vars['appVersion']))
529
nsis_vars['CONFIG_OUTPUT_FILE'] = output_file
530
nsis_vars['CONFIG_TWOSTAGE'] = "Yes"
531
nsis_vars.pop('MIROBAR_EXE', None)
533
nsis_args = ["/D%s=%s" % (k, v) for (k, v) in nsis_vars.iteritems()]
534
nsis_args.append(os.path.join(self.dist_dir, "Miro.nsi"))
536
if os.access(output_file, os.F_OK):
537
os.remove(output_file)
538
subprocess.call([NSIS_PATH] + nsis_args)
540
zip_path = os.path.join(self.dist_dir, "%s-Contents-%s.zip" %
541
(template_vars['shortAppName'], template_vars['appVersion']))
542
self.zipfile = zip.ZipFile(zip_path, 'w', zip.ZIP_DEFLATED)
543
self.add_file(nsis_vars['CONFIG_EXECUTABLE'])
544
self.add_file(nsis_vars['CONFIG_ICON'])
545
self.add_file(nsis_vars['CONFIG_MOVIE_DATA_EXECUTABLE'])
546
self.add_glob("*.dll")
548
self.add_directory("defaults")
549
self.add_directory("resources")
550
self.add_directory("xulrunner")
554
def add_glob(self, wildcard):
555
wildcard = os.path.join(self.dist_dir, wildcard)
556
length = len(self.dist_dir)
557
for filename in iglob(wildcard):
558
if filename[:length] == self.dist_dir:
559
filename = filename[length:]
560
while (len(filename) > 0
561
and (filename[0] == '/' or filename[0] == '\\')):
562
filename = filename[1:]
563
print "Compressing %s" % filename
564
self.zipfile.write(os.path.join(self.dist_dir, filename), filename)
566
def add_file(self, filename):
567
length = len(self.dist_dir)
568
if filename[:length] == self.dist_dir:
569
filename = filename[length:]
570
while (len(filename) > 0
571
and (filename[0] == '/' or filename[0] == '\\')):
572
filename = filename[1:]
573
print "Compressing %s" % filename
574
self.zipfile.write(os.path.join(self.dist_dir, filename), filename)
576
def add_directory(self, dirname):
577
for root, dirs, files in os.walk(os.path.join(self.dist_dir, dirname)):
579
self.add_file(os.path.join(root, name))
581
if __name__ == "__main__":
586
'dest_base': template_vars['shortAppName'],
587
'icon_resources': [(0, "Miro.ico")],
590
'script': 'Miro_Downloader.py',
591
'dest_base': '%s_Downloader' % template_vars['shortAppName'],
592
'icon_resources': [(0, "Miro.ico")],
597
'script': 'moviedata_util.py',
598
'dest_base': '%s_MovieData' % template_vars['shortAppName'],
599
'icon_resources': [(0, "Miro.ico")],
602
'script': 'mirotest.py',
603
'dest_base': 'mirotest',
604
'icon_resources': [(0, "Miro.ico")],
607
ext_modules=ext_modules,
611
'miro.dl_daemon.private',
613
'miro.frontends.widgets',
614
'miro.frontends.widgets.gtk',
617
'miro.plat.renderers',
618
'miro.plat.frontends',
619
'miro.plat.frontends.widgets',
622
'miro': portable_dir,
623
'miro.plat': platform_package_dir,
625
data_files=data_files,
627
'build_ext': build_ext,
628
'install_data': install_data,
629
'bdist_miro': bdist_miro,
630
'bdist_nsis': bdist_nsis,
631
'bdist_test': bdist_test,
639
'includes': 'cairo, pango, pangocairo, atk, gobject, gio, libtorrent',