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
platform/gtk-x11. 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, 'portable')
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, 'platform', 'gtk-x11')
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 package,
105
# but at this point, they are in a package named "portable", so let's hack it
107
sys.modules['miro'] = portable
109
sys.modules['miro'].plat = plat
111
# little hack to get the version from the current app.config.template
112
from miro import util
113
app_config = os.path.join(resource_dir, 'app.config.template')
114
appVersion = util.read_simple_config_file(app_config)['appVersion']
117
if 'bdist_rpm' in sys.argv:
118
appVersion = appVersion.replace('-', '_')
121
"""Does a best-effort attempt to return the login of the user running the
125
return os.environ['LOGNAME']
129
return os.environ['USER']
132
pwd.getpwuid(os.getuid())[0]
141
def write_file(path, contents):
148
def expand_file_contents(path, **values):
149
"""Do a string expansion on the contents of a file using the same rules as
150
string.Template from the standard library.
152
template = Template(read_file(path))
153
expanded = template.substitute(**values)
154
write_file(path, expanded)
156
def get_command_output(cmd, warnOnStderr=True, warnOnReturnCode=True):
157
"""Wait for a command and return its output. Check for common errors and
158
raise an exception if one of these occurs.
160
p = subprocess.Popen(cmd, shell=True, close_fds=True,
161
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
162
stdout, stderr = p.communicate()
163
if warnOnStderr and stderr != '':
164
raise RuntimeError("%s outputted the following error:\n%s" % (cmd, stderr))
165
if warnOnReturnCode and p.returncode != 0:
166
raise RuntimeError("%s had non-zero return code %d" % (cmd, p.returncode))
169
def parse_pkg_config(command, components, options_dict = None):
170
"""Helper function to parse compiler/linker arguments from
171
pkg-config and update include_dirs, library_dirs, etc.
173
We return a dict with the following keys, which match up with keyword
174
arguments to the setup function: include_dirs, library_dirs, libraries,
177
Command is the command to run (pkg-config, etc).
178
Components is a string that lists the components to get options for.
180
If options_dict is passed in, we add options to it, instead of starting
183
if options_dict is None:
189
'extra_compile_args' : []
191
commandLine = "%s --cflags --libs %s" % (command, components)
192
output = get_command_output(commandLine).strip()
193
for comp in output.split():
194
prefix, rest = comp[:2], comp[2:]
196
options_dict['include_dirs'].append(rest)
198
options_dict['library_dirs'].append(rest)
200
options_dict['libraries'].append(rest)
202
options_dict['extra_compile_args'].append(comp)
204
commandLine = "%s --variable=libdir %s" % (command, components)
205
output = get_command_output(commandLine).strip()
206
options_dict['runtime_dirs'].append(output)
210
def package_exists(package_name):
212
Return True if the package is present in the system. False otherwise.
213
The check is made with pkg-config.
215
# pkg-config returns 0 if the package is present
216
return subprocess.call(['pkg-config', '--exists', package_name]) == 0
219
f = open(os.path.join(platform_dir, "miro"), "w")
222
# This file is generated by setup.py.
234
echo "DEBUGGING MODE."
235
PYTHON=`which python`
240
echo "gdb cannot be found on your path. aborting....";
244
$GDB -ex 'set breakpoint pending on' -ex 'run' --args $PYTHON ./miro.real --sync "$@"
252
#### Xlib Extension ####
254
Extension("miro.plat.xlibhelper",
255
[ os.path.join(platform_package_dir,'xlibhelper.pyx') ],
256
library_dirs = ['/usr/X11R6/lib'],
261
Extension("miro.frontends.widgets.gtk.pygtkhacks",
262
[ os.path.join(portable_frontend_dir, 'widgets', 'gtk',
264
**parse_pkg_config('pkg-config',
265
'pygobject-2.0 gtk+-2.0 glib-2.0 gthread-2.0')
268
#### Build the data_files list ####
270
return [f for f in glob(os.path.join(path, '*')) if os.path.isfile(f)]
273
# append the root resource directory.
274
# filter out app.config.template (which is handled specially)
275
files = [f for f in listfiles(resource_dir) \
276
if os.path.basename(f) != 'app.config.template']
277
data_files.append(('/usr/share/miro/resources/', files))
278
# handle the sub directories.
279
for dir in ('searchengines', 'images', 'testdata',
280
os.path.join('testdata', 'stripperdata'),
281
os.path.join('testdata', 'locale', 'fr', 'LC_MESSAGES')):
282
source_dir = os.path.join(resource_dir, dir)
283
dest_dir = os.path.join('/usr/share/miro/resources/', dir)
284
data_files.append((dest_dir, listfiles(source_dir)))
286
for mem in ["24", "48", "72", "128"]:
287
d = os.path.join("icons", "hicolor", "%sx%s" % (mem, mem), "apps")
288
source = os.path.join(platform_dir, d, "miro.png")
289
dest = os.path.join("/usr/share/", d)
290
data_files.append((dest, [source]))
292
# add ADOPTERS file, the desktop file, mime data, and man page
294
('/usr/share/miro/resources',
295
[os.path.join(root_dir, 'ADOPTERS')]),
296
('/usr/share/pixmaps',
297
glob(os.path.join(platform_dir, 'miro.xpm'))),
298
('/usr/share/applications',
299
[os.path.join(platform_dir, 'miro.desktop')]),
300
('/usr/share/mime/packages',
301
[os.path.join(platform_dir, 'miro.xml')]),
302
('/usr/share/man/man1',
303
[os.path.join(platform_dir, 'miro.1.gz')]),
304
('/usr/share/man/man1',
305
[os.path.join(platform_dir, 'miro.real.1.gz')]),
309
# if we're not doing "python setup.py clean", then we can do a bunch of things
310
# that have file-related side-effects
311
if not "clean" in sys.argv:
314
os.system ("gzip -9 < %s > %s" % (os.path.join(platform_dir, 'miro.1'), os.path.join(platform_dir, 'miro.1.gz')))
315
# copy miro.1.gz to miro.real.1.gz so that lintian complains less
316
os.system ("cp %s %s" % (os.path.join(platform_dir, 'miro.1.gz'), os.path.join(platform_dir, 'miro.real.1.gz')))
319
#### Our specialized install_data command ####
320
class install_data(distutils.command.install_data.install_data):
321
"""install_data extends to default implementation so that it automatically
322
installs app.config from app.config.template.
325
def install_app_config(self):
326
source = os.path.join(resource_dir, 'app.config.template')
327
dest = '/usr/share/miro/resources/app.config'
329
config_file = util.read_simple_config_file(source)
330
print "Trying to figure out the git revision...."
331
if config_file["appVersion"].endswith("git"):
332
revision = util.query_revision()
335
revisionurl = "unknown"
336
revisionnum = "unknown"
338
revisionurl = revision[0]
339
revisionnum = revision[1]
340
revision = "%s - %s" % (revisionurl, revisionnum)
345
print "Using %s" % revisionnum
348
dest = change_root(self.root, dest)
349
self.mkpath(os.path.dirname(dest))
350
# We don't use the dist utils copy_file() because it only copies
351
# the file if the timestamp is newer
352
shutil.copyfile(source, dest)
353
expand_file_contents(dest, APP_REVISION=revision,
354
APP_REVISION_NUM=revisionnum,
355
APP_REVISION_URL=revisionurl,
356
APP_PLATFORM='gtk-x11',
357
BUILD_MACHINE="%s@%s" % (getlogin(),
359
BUILD_TIME=str(time.time()),
361
self.outfiles.append(dest)
363
locale_dir = os.path.join (resource_dir, "locale")
365
for source in glob (os.path.join (locale_dir, "*.mo")):
366
lang = os.path.basename(source)[:-3]
367
if 'LINGUAS' in os.environ and lang not in os.environ['LINGUAS']:
369
dest = '/usr/share/locale/%s/LC_MESSAGES/miro.mo' % lang
371
dest = change_root(self.root, dest)
372
self.mkpath(os.path.dirname(dest))
373
self.copy_file(source, dest)
374
self.outfiles.append(dest)
377
distutils.command.install_data.install_data.run(self)
378
self.install_app_config()
381
class test_system(Command):
382
description = "Allows you to test configurations without compiling or running."
385
def initialize_options(self):
388
def finalize_options(self):
392
# FIXME - try importing and all that other stuff to make sure
393
# we have most of the pieces here?
396
#### install_theme installs a specified theme .zip
397
class install_theme(Command):
398
description = 'Install a provided theme to /usr/share/miro/themes'
399
user_options = [("theme=", None, 'ZIP file containing the theme')]
401
def initialize_options(self):
404
def finalize_options(self):
405
if self.theme is None:
406
raise DistutilsOptionError, "must supply a theme ZIP file"
407
if not os.path.exists(self.theme):
408
raise DistutilsOptionError, "theme file does not exist"
410
if not zipfile.is_zipfile(self.theme):
411
raise DistutilsOptionError, "theme file is not a ZIP file"
412
zf = zipfile.ZipFile(self.theme)
413
appConfig = zf.read('app.config')
415
for line in appConfig.split('\n'):
417
name, value = line.split('=', 1)
419
value = value.lstrip()
420
if name == 'themeName':
422
if themeName is None:
423
raise DistutilsOptionError, "invalid theme file"
425
self.theme_name = themeName
426
self.theme_dir = '/usr/share/miro/themes/%s' % themeName
429
if os.path.exists(self.theme_dir):
430
shutil.rmtree(self.theme_dir)
431
os.makedirs(self.theme_dir)
432
for name in self.zipfile.namelist():
433
if name.startswith('xul/'):
434
# ignore XUL stuff, we don't need it on Linux
436
print 'installing', os.path.join(self.theme_dir, name)
438
os.makedirs(os.path.join(self.theme_dir, name))
440
f = file(os.path.join(self.theme_dir, name), 'wb')
441
f.write(self.zipfile.read(name))
443
print """%s theme installed.
445
To use this theme, run:
448
""" % (self.theme_name, self.theme_name)
450
class clean(Command):
451
description = 'Cleans the build and dist directories'
454
def initialize_options(self):
457
def finalize_options(self):
461
if os.path.exists('./build/'):
462
print "removing build directory"
463
shutil.rmtree('./build/')
465
if os.path.exists('./dist/'):
466
print "removing dist directory"
467
shutil.rmtree('./dist/')
470
ext_modules.append(xlib_ext)
471
ext_modules.append(pygtkhacks_ext)
476
author='Participatory Culture Foundation',
477
author_email='feedback@pculture.org',
478
url='http://www.getmiro.com/',
479
download_url='http://www.getmiro.com/downloads/',
481
os.path.join(platform_dir, 'miro'),
482
os.path.join(platform_dir, 'miro.real')
484
data_files=data_files,
485
ext_modules=ext_modules,
490
'miro.dl_daemon.private',
492
'miro.frontends.cli',
493
'miro.frontends.shell',
494
'miro.frontends.widgets',
495
'miro.frontends.widgets.gtk',
497
'miro.plat.frontends',
498
'miro.plat.frontends.widgets',
499
'miro.plat.renderers',
502
'miro': portable_dir,
503
'miro.test': test_dir,
504
'miro.plat': platform_package_dir,
507
'test_system': test_system,
508
'build_ext': build_ext,
509
'install_data': install_data,
510
'install_theme': install_theme,