3
# Miro - an RSS based video player application
4
# Copyright (C) 2005-2010 Participatory Culture Foundation
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
# In addition, as a special exception, the copyright holders give
21
# permission to link the code of portions of this program with the OpenSSL
24
# You must obey the GNU General Public License in all respects for all of
25
# the code used other than OpenSSL. If you modify file(s) with this
26
# exception, you may extend this exception to your version of the file(s),
27
# but you are not obligated to do so. If you do not wish to do so, delete
28
# this exception statement from your version. If you delete this exception
29
# statement from all source files in the program, then also delete it here.
31
################################################################
32
## No user-serviceable parts inside ##
33
################################################################
37
# verify we have required bits for compiling Miro
40
from Pyrex.Compiler import Version
41
if Version.version.split(".") < ["0", "9", "6", "4"]:
42
print "Pyrex 0.9.6.4 or greater required. You have version %s." % Version.version
45
print "Pyrex not found. Please install Pyrex."
48
from distutils.cmd import Command
49
from distutils.core import setup
50
from distutils.extension import Extension
51
from distutils.errors import DistutilsOptionError
52
from distutils.util import change_root
53
from distutils import dir_util, log, sysconfig
55
from string import Template
56
import distutils.command.install_data
65
from Pyrex.Distutils import build_ext
67
#### useful paths to have around ####
70
bdist_rpm and possibly other commands copies setup.py into a subdir of
71
linux. This makes it hard to find the root directory. We work
72
our way up the path until our is_root_dir test passes.
74
return os.path.exists(os.path.join(d, "MIRO_ROOT"))
77
root_try = os.path.abspath(os.path.dirname(__file__))
79
if is_root_dir(root_try):
83
raise RuntimeError("Couldn't find Miro root directory")
84
root_try = os.path.abspath(os.path.join(root_try, '..'))
87
root_dir = get_root_dir()
88
portable_dir = os.path.join(root_dir, 'lib')
89
portable_frontend_dir = os.path.join(portable_dir, 'frontends')
90
portable_xpcom_dir = os.path.join(portable_frontend_dir, 'widgets', 'gtk',
92
dl_daemon_dir = os.path.join(portable_dir, 'dl_daemon')
93
test_dir = os.path.join(portable_dir, 'test')
94
resource_dir = os.path.join(root_dir, 'resources')
95
platform_dir = os.path.join(root_dir, 'linux')
96
platform_package_dir = os.path.join(platform_dir, 'plat')
97
platform_widgets_dir = os.path.join(platform_package_dir, 'frontends',
100
# insert the root_dir to the beginning of sys.path so that we can
101
# pick up portable and other packages
102
sys.path.insert(0, root_dir)
104
# later when we install the portable modules, they will be in the miro
105
# package, but at this point, they are in a package named "lib", so
108
sys.modules['miro'] = lib
110
sys.modules['miro'].plat = plat
112
# little hack to get the version from the current app.config.template
113
from miro import util
114
app_config = os.path.join(resource_dir, 'app.config.template')
115
appVersion = util.read_simple_config_file(app_config)['appVersion']
118
if 'bdist_rpm' in sys.argv:
119
appVersion = appVersion.replace('-', '_')
122
"""Does a best-effort attempt to return the login of the user running the
126
return os.environ['LOGNAME']
130
return os.environ['USER']
133
pwd.getpwuid(os.getuid())[0]
142
def write_file(path, contents):
149
def expand_file_contents(path, **values):
150
"""Do a string expansion on the contents of a file using the same rules as
151
string.Template from the standard library.
153
template = Template(read_file(path))
154
expanded = template.substitute(**values)
155
write_file(path, expanded)
157
def get_command_output(cmd, warnOnStderr=True, warnOnReturnCode=True):
158
"""Wait for a command and return its output. Check for common errors and
159
raise an exception if one of these occurs.
161
p = subprocess.Popen(cmd, shell=True, close_fds=True,
162
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
163
stdout, stderr = p.communicate()
164
if warnOnStderr and stderr != '':
165
raise RuntimeError("%s outputted the following error:\n%s" % (cmd, stderr))
166
if warnOnReturnCode and p.returncode != 0:
167
raise RuntimeError("%s had non-zero return code %d" % (cmd, p.returncode))
170
def parse_pkg_config(command, components, options_dict = None):
171
"""Helper function to parse compiler/linker arguments from
172
pkg-config and update include_dirs, library_dirs, etc.
174
We return a dict with the following keys, which match up with keyword
175
arguments to the setup function: include_dirs, library_dirs, libraries,
178
Command is the command to run (pkg-config, etc).
179
Components is a string that lists the components to get options for.
181
If options_dict is passed in, we add options to it, instead of starting
184
if options_dict is None:
190
'extra_compile_args' : []
192
commandLine = "%s --cflags --libs %s" % (command, components)
193
output = get_command_output(commandLine).strip()
194
for comp in output.split():
195
prefix, rest = comp[:2], comp[2:]
197
options_dict['include_dirs'].append(rest)
199
options_dict['library_dirs'].append(rest)
201
options_dict['libraries'].append(rest)
203
options_dict['extra_compile_args'].append(comp)
205
commandLine = "%s --variable=libdir %s" % (command, components)
206
output = get_command_output(commandLine).strip()
207
options_dict['runtime_dirs'].append(output)
211
def package_exists(package_name):
213
Return True if the package is present in the system. False otherwise.
214
The check is made with pkg-config.
216
# pkg-config returns 0 if the package is present
217
return subprocess.call(['pkg-config', '--exists', package_name]) == 0
220
f = open(os.path.join(platform_dir, "miro"), "w")
223
# This file is generated by setup.py.
235
echo "DEBUGGING MODE."
236
PYTHON=`which python`
241
echo "gdb cannot be found on your path. aborting....";
245
$GDB -ex 'set breakpoint pending on' -ex 'run' --args $PYTHON /usr/bin/miro.real --sync "$@"
253
#### Xlib Extension ####
255
Extension("miro.plat.xlibhelper",
256
[os.path.join(platform_package_dir, 'xlibhelper.pyx')],
257
library_dirs = ['/usr/X11R6/lib'],
262
Extension("miro.frontends.widgets.gtk.pygtkhacks",
263
[os.path.join(portable_frontend_dir, 'widgets', 'gtk',
265
**parse_pkg_config('pkg-config',
266
'pygobject-2.0 gtk+-2.0 glib-2.0 gthread-2.0')
269
webkitgtkhacks_ext = \
270
Extension("miro.frontends.widgets.gtk.webkitgtkhacks",
271
[os.path.join(portable_frontend_dir, 'widgets', 'gtk',
272
'webkitgtkhacks.pyx')],
273
**parse_pkg_config('pkg-config',
274
'gtk+-2.0 webkit-1.0')
277
#### Build the data_files list ####
279
return [f for f in glob(os.path.join(path, '*')) if os.path.isfile(f)]
282
# append the root resource directory.
283
# filter out app.config.template (which is handled specially)
284
files = [f for f in listfiles(resource_dir) \
285
if os.path.basename(f) != 'app.config.template']
286
data_files.append(('/usr/share/miro/resources/', files))
287
# handle the sub directories.
288
for dir in ('searchengines', 'images', 'testdata', 'conversions',
289
os.path.join('testdata', 'stripperdata'),
290
os.path.join('testdata', 'httpserver'),
291
os.path.join('testdata', 'locale', 'fr', 'LC_MESSAGES')):
292
source_dir = os.path.join(resource_dir, dir)
293
dest_dir = os.path.join('/usr/share/miro/resources/', dir)
294
data_files.append((dest_dir, listfiles(source_dir)))
296
for mem in ["24", "48", "72", "128"]:
297
d = os.path.join("icons", "hicolor", "%sx%s" % (mem, mem), "apps")
298
source = os.path.join(platform_dir, d, "miro.png")
299
dest = os.path.join("/usr/share/", d)
300
data_files.append((dest, [source]))
302
# add ADOPTERS file, the desktop file, mime data, and man page
304
('/usr/share/miro/resources',
305
[os.path.join(root_dir, 'CREDITS')]),
306
('/usr/share/miro/resources',
307
[os.path.join(root_dir, 'ADOPTERS')]),
308
('/usr/share/pixmaps',
309
glob(os.path.join(platform_dir, 'miro.xpm'))),
310
('/usr/share/applications',
311
[os.path.join(platform_dir, 'miro.desktop')]),
312
('/usr/share/mime/packages',
313
[os.path.join(platform_dir, 'miro.xml')]),
314
('/usr/share/man/man1',
315
[os.path.join(platform_dir, 'miro.1.gz')]),
316
('/usr/share/man/man1',
317
[os.path.join(platform_dir, 'miro.real.1.gz')]),
321
# if we're not doing "python setup.py clean", then we can do a bunch of things
322
# that have file-related side-effects
323
if not "clean" in sys.argv:
326
os.system ("gzip -9 < %s > %s" % (os.path.join(platform_dir, 'miro.1'), os.path.join(platform_dir, 'miro.1.gz')))
327
# copy miro.1.gz to miro.real.1.gz so that lintian complains less
328
os.system ("cp %s %s" % (os.path.join(platform_dir, 'miro.1.gz'), os.path.join(platform_dir, 'miro.real.1.gz')))
331
#### Our specialized install_data command ####
332
class install_data(distutils.command.install_data.install_data):
333
"""install_data extends to default implementation so that it automatically
334
installs app.config from app.config.template.
337
def install_app_config(self):
338
source = os.path.join(resource_dir, 'app.config.template')
339
dest = '/usr/share/miro/resources/app.config'
341
config_file = util.read_simple_config_file(source)
342
print "Trying to figure out the git revision...."
343
if config_file["appVersion"].endswith("git"):
344
revision = util.query_revision()
347
revisionurl = "unknown"
348
revisionnum = "unknown"
350
revisionurl = revision[0]
351
revisionnum = revision[1]
352
revision = "%s - %s" % (revisionurl, revisionnum)
357
print "Using %s" % revisionnum
360
dest = change_root(self.root, dest)
361
self.mkpath(os.path.dirname(dest))
362
# We don't use the dist utils copy_file() because it only copies
363
# the file if the timestamp is newer
364
shutil.copyfile(source, dest)
365
expand_file_contents(dest, APP_REVISION=revision,
366
APP_REVISION_NUM=revisionnum,
367
APP_REVISION_URL=revisionurl,
368
APP_PLATFORM='linux',
369
BUILD_MACHINE="%s@%s" % (getlogin(),
371
BUILD_TIME=str(time.time()),
373
self.outfiles.append(dest)
375
locale_dir = os.path.join (resource_dir, "locale")
377
for source in glob (os.path.join (locale_dir, "*.mo")):
378
lang = os.path.basename(source)[:-3]
379
if 'LINGUAS' in os.environ and lang not in os.environ['LINGUAS']:
381
dest = '/usr/share/locale/%s/LC_MESSAGES/miro.mo' % lang
383
dest = change_root(self.root, dest)
384
self.mkpath(os.path.dirname(dest))
385
self.copy_file(source, dest)
386
self.outfiles.append(dest)
389
distutils.command.install_data.install_data.run(self)
390
self.install_app_config()
393
class test_system(Command):
394
description = "Allows you to test configurations without compiling or running."
397
def initialize_options(self):
400
def finalize_options(self):
404
# FIXME - try importing and all that other stuff to make sure
405
# we have most of the pieces here?
408
#### install_theme installs a specified theme .zip
409
class install_theme(Command):
410
description = 'Install a provided theme to /usr/share/miro/themes'
411
user_options = [("theme=", None, 'ZIP file containing the theme')]
413
def initialize_options(self):
416
def finalize_options(self):
417
if self.theme is None:
418
raise DistutilsOptionError, "must supply a theme ZIP file"
419
if not os.path.exists(self.theme):
420
raise DistutilsOptionError, "theme file does not exist"
422
if not zipfile.is_zipfile(self.theme):
423
raise DistutilsOptionError, "theme file is not a ZIP file"
424
zf = zipfile.ZipFile(self.theme)
425
appConfig = zf.read('app.config')
427
for line in appConfig.split('\n'):
429
name, value = line.split('=', 1)
431
value = value.lstrip()
432
if name == 'themeName':
434
if themeName is None:
435
raise DistutilsOptionError, "invalid theme file"
437
self.theme_name = themeName
438
self.theme_dir = '/usr/share/miro/themes/%s' % themeName
441
if os.path.exists(self.theme_dir):
442
shutil.rmtree(self.theme_dir)
443
os.makedirs(self.theme_dir)
444
for name in self.zipfile.namelist():
445
if name.startswith('xul/'):
446
# ignore XUL stuff, we don't need it on Linux
448
print 'installing', os.path.join(self.theme_dir, name)
450
os.makedirs(os.path.join(self.theme_dir, name))
452
f = file(os.path.join(self.theme_dir, name), 'wb')
453
f.write(self.zipfile.read(name))
455
print """%s theme installed.
457
To use this theme, run:
460
""" % (self.theme_name, self.theme_name)
462
class clean(Command):
463
description = 'Cleans the build and dist directories'
466
def initialize_options(self):
469
def finalize_options(self):
473
if os.path.exists('./build/'):
474
print "removing build directory"
475
shutil.rmtree('./build/')
477
if os.path.exists('./dist/'):
478
print "removing dist directory"
479
shutil.rmtree('./dist/')
482
ext_modules.append(xlib_ext)
483
ext_modules.append(pygtkhacks_ext)
484
ext_modules.append(webkitgtkhacks_ext)
489
author='Participatory Culture Foundation',
490
author_email='feedback@pculture.org',
491
url='http://www.getmiro.com/',
492
download_url='http://www.getmiro.com/downloads/',
494
os.path.join(platform_dir, 'miro'),
495
os.path.join(platform_dir, 'miro.real')
497
data_files=data_files,
498
ext_modules=ext_modules,
503
'miro.dl_daemon.private',
505
'miro.frontends.cli',
506
'miro.frontends.shell',
507
'miro.frontends.widgets',
508
'miro.frontends.widgets.gtk',
510
'miro.plat.frontends',
511
'miro.plat.frontends.widgets',
512
'miro.plat.renderers',
515
'miro': portable_dir,
516
'miro.test': test_dir,
517
'miro.plat': platform_package_dir,
520
'test_system': test_system,
521
'build_ext': build_ext,
522
'install_data': install_data,
523
'install_theme': install_theme,