~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to linux/setup.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
# Miro - an RSS based video player application
 
4
# Copyright (C) 2005-2010 Participatory Culture Foundation
 
5
#
 
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.
 
10
#
 
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.
 
15
#
 
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
 
19
#
 
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
 
22
# library.
 
23
#
 
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.
 
30
 
 
31
################################################################
 
32
## No user-serviceable parts inside                           ##
 
33
################################################################
 
34
 
 
35
import sys
 
36
 
 
37
# verify we have required bits for compiling Miro
 
38
 
 
39
try:
 
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
 
43
        sys.exit(1)
 
44
except ImportError:
 
45
    print "Pyrex not found.  Please install Pyrex."
 
46
    sys.exit(1)
 
47
 
 
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
 
54
from glob import glob
 
55
from string import Template
 
56
import distutils.command.install_data
 
57
import os
 
58
import pwd
 
59
import subprocess
 
60
import platform
 
61
import re
 
62
import time
 
63
import shutil
 
64
 
 
65
from Pyrex.Distutils import build_ext
 
66
 
 
67
#### useful paths to have around ####
 
68
def is_root_dir(d):
 
69
    """
 
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.
 
73
    """
 
74
    return os.path.exists(os.path.join(d, "MIRO_ROOT"))
 
75
 
 
76
def get_root_dir():
 
77
    root_try = os.path.abspath(os.path.dirname(__file__))
 
78
    while True:
 
79
        if is_root_dir(root_try):
 
80
            root_dir = root_try
 
81
            break
 
82
        if root_try == '/':
 
83
            raise RuntimeError("Couldn't find Miro root directory")
 
84
        root_try = os.path.abspath(os.path.join(root_try, '..'))
 
85
    return root_dir
 
86
 
 
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',
 
91
                                  'xpcom')
 
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',
 
98
                                    'widgets')
 
99
 
 
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)
 
103
 
 
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
 
106
# let's hack it
 
107
import lib
 
108
sys.modules['miro'] = lib
 
109
import plat
 
110
sys.modules['miro'].plat = plat
 
111
 
 
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']
 
116
 
 
117
# RPM hack
 
118
if 'bdist_rpm' in sys.argv:
 
119
    appVersion = appVersion.replace('-', '_')
 
120
 
 
121
def getlogin():
 
122
    """Does a best-effort attempt to return the login of the user running the
 
123
    script.
 
124
    """
 
125
    try:
 
126
        return os.environ['LOGNAME']
 
127
    except KeyError:
 
128
        pass
 
129
    try:
 
130
        return os.environ['USER']
 
131
    except KeyError:
 
132
        pass
 
133
    pwd.getpwuid(os.getuid())[0]
 
134
 
 
135
def read_file(path):
 
136
    f = open(path)
 
137
    try:
 
138
        return f.read()
 
139
    finally:
 
140
        f.close()
 
141
 
 
142
def write_file(path, contents):
 
143
    f = open(path, 'w')
 
144
    try:
 
145
        f.write(contents)
 
146
    finally:
 
147
        f.close()
 
148
 
 
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.
 
152
    """
 
153
    template = Template(read_file(path))
 
154
    expanded = template.substitute(**values)
 
155
    write_file(path, expanded)
 
156
 
 
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.
 
160
    """
 
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))
 
168
    return stdout
 
169
 
 
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.
 
173
 
 
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,
 
176
    extra_compile_args.
 
177
 
 
178
    Command is the command to run (pkg-config, etc).
 
179
    Components is a string that lists the components to get options for.
 
180
 
 
181
    If options_dict is passed in, we add options to it, instead of starting
 
182
    from scratch.
 
183
    """
 
184
    if options_dict is None:
 
185
        options_dict = {
 
186
            'include_dirs' : [],
 
187
            'library_dirs' : [],
 
188
            'runtime_dirs' : [],
 
189
            'libraries' : [],
 
190
            'extra_compile_args' : []
 
191
        }
 
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:]
 
196
        if prefix == '-I':
 
197
            options_dict['include_dirs'].append(rest)
 
198
        elif prefix == '-L':
 
199
            options_dict['library_dirs'].append(rest)
 
200
        elif prefix == '-l':
 
201
            options_dict['libraries'].append(rest)
 
202
        else:
 
203
            options_dict['extra_compile_args'].append(comp)
 
204
 
 
205
    commandLine = "%s --variable=libdir %s" % (command, components)
 
206
    output = get_command_output(commandLine).strip()
 
207
    options_dict['runtime_dirs'].append(output)
 
208
 
 
209
    return options_dict
 
210
 
 
211
def package_exists(package_name):
 
212
    """
 
213
    Return True if the package is present in the system.  False otherwise.
 
214
    The check is made with pkg-config.
 
215
    """
 
216
    # pkg-config returns 0 if the package is present
 
217
    return subprocess.call(['pkg-config', '--exists', package_name]) == 0
 
218
 
 
219
def generate_miro():
 
220
    f = open(os.path.join(platform_dir, "miro"), "w")
 
221
    f.write(
 
222
"""#!/bin/sh
 
223
# This file is generated by setup.py.
 
224
DEBUG=0
 
225
 
 
226
for arg in $@
 
227
do
 
228
    case $arg in
 
229
    "--debug")    DEBUG=1;;
 
230
    esac
 
231
done
 
232
 
 
233
if [ $DEBUG = 1 ]
 
234
then
 
235
    echo "DEBUGGING MODE."
 
236
    PYTHON=`which python`
 
237
    GDB=`which gdb`
 
238
 
 
239
    if [ -z $GDB ]
 
240
    then
 
241
        echo "gdb cannot be found on your path.  aborting....";
 
242
        exit;
 
243
    fi
 
244
 
 
245
    $GDB -ex 'set breakpoint pending on' -ex 'run' --args $PYTHON /usr/bin/miro.real --sync "$@"
 
246
else
 
247
    miro.real "$@"
 
248
fi
 
249
""")
 
250
    f.close()
 
251
 
 
252
 
 
253
#### Xlib Extension ####
 
254
xlib_ext = \
 
255
    Extension("miro.plat.xlibhelper",
 
256
        [os.path.join(platform_package_dir, 'xlibhelper.pyx')],
 
257
        library_dirs = ['/usr/X11R6/lib'],
 
258
        libraries = ['X11'],
 
259
    )
 
260
 
 
261
pygtkhacks_ext = \
 
262
    Extension("miro.frontends.widgets.gtk.pygtkhacks",
 
263
        [os.path.join(portable_frontend_dir, 'widgets', 'gtk',
 
264
                      'pygtkhacks.pyx')],
 
265
        **parse_pkg_config('pkg-config',
 
266
            'pygobject-2.0 gtk+-2.0 glib-2.0 gthread-2.0')
 
267
    )
 
268
 
 
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')
 
275
    )
 
276
 
 
277
#### Build the data_files list ####
 
278
def listfiles(path):
 
279
    return [f for f in glob(os.path.join(path, '*')) if os.path.isfile(f)]
 
280
 
 
281
data_files = []
 
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)))
 
295
 
 
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]))
 
301
 
 
302
# add ADOPTERS file, the desktop file, mime data, and man page
 
303
data_files += [
 
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')]),
 
318
]
 
319
 
 
320
 
 
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:
 
324
    generate_miro()
 
325
    # gzip the man page
 
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')))
 
329
 
 
330
 
 
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.
 
335
    """
 
336
 
 
337
    def install_app_config(self):
 
338
        source = os.path.join(resource_dir, 'app.config.template')
 
339
        dest = '/usr/share/miro/resources/app.config'
 
340
 
 
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()
 
345
            if revision is None:
 
346
                revision = "unknown"
 
347
                revisionurl = "unknown"
 
348
                revisionnum = "unknown"
 
349
            else:
 
350
                revisionurl = revision[0]
 
351
                revisionnum = revision[1]
 
352
                revision = "%s - %s" % (revisionurl, revisionnum)
 
353
        else:
 
354
            revisionurl = ""
 
355
            revisionnum = ""
 
356
            revision = ""
 
357
        print "Using %s" % revisionnum
 
358
 
 
359
        if self.root:
 
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(),
 
370
                                                      os.uname()[1]),
 
371
                             BUILD_TIME=str(time.time()),
 
372
                             MOZILLA_LIB_PATH="")
 
373
        self.outfiles.append(dest)
 
374
 
 
375
        locale_dir = os.path.join (resource_dir, "locale")
 
376
 
 
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']:
 
380
                continue
 
381
            dest = '/usr/share/locale/%s/LC_MESSAGES/miro.mo' % lang
 
382
            if self.root:
 
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)
 
387
 
 
388
    def run(self):
 
389
        distutils.command.install_data.install_data.run(self)
 
390
        self.install_app_config()
 
391
 
 
392
 
 
393
class test_system(Command):
 
394
    description = "Allows you to test configurations without compiling or running."
 
395
    user_options = []
 
396
 
 
397
    def initialize_options(self):
 
398
        pass
 
399
 
 
400
    def finalize_options(self):
 
401
        pass
 
402
 
 
403
    def run(self):
 
404
        # FIXME - try importing and all that other stuff to make sure
 
405
        # we have most of the pieces here?
 
406
        pass
 
407
 
 
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')]
 
412
 
 
413
    def initialize_options(self):
 
414
        self.theme = None
 
415
 
 
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"
 
421
        import zipfile
 
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')
 
426
        themeName = None
 
427
        for line in appConfig.split('\n'):
 
428
            if '=' in line:
 
429
                name, value = line.split('=', 1)
 
430
                name = name.strip()
 
431
                value = value.lstrip()
 
432
                if name == 'themeName':
 
433
                    themeName = value
 
434
        if themeName is None:
 
435
            raise DistutilsOptionError, "invalid theme file"
 
436
        self.zipfile = zf
 
437
        self.theme_name = themeName
 
438
        self.theme_dir = '/usr/share/miro/themes/%s' % themeName
 
439
 
 
440
    def run(self):
 
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
 
447
                continue
 
448
            print 'installing', os.path.join(self.theme_dir, name)
 
449
            if name[-1] == '/':
 
450
                os.makedirs(os.path.join(self.theme_dir, name))
 
451
            else:
 
452
                f = file(os.path.join(self.theme_dir, name), 'wb')
 
453
                f.write(self.zipfile.read(name))
 
454
                f.close()
 
455
        print """%s theme installed.
 
456
 
 
457
To use this theme, run:
 
458
 
 
459
    miro --theme="%s"
 
460
""" % (self.theme_name, self.theme_name)
 
461
 
 
462
class clean(Command):
 
463
    description = 'Cleans the build and dist directories'
 
464
    user_options = []
 
465
 
 
466
    def initialize_options(self):
 
467
        pass
 
468
 
 
469
    def finalize_options(self):
 
470
        pass
 
471
 
 
472
    def run(self):
 
473
        if os.path.exists('./build/'):
 
474
            print "removing build directory"
 
475
            shutil.rmtree('./build/')
 
476
 
 
477
        if os.path.exists('./dist/'):
 
478
            print "removing dist directory"
 
479
            shutil.rmtree('./dist/')
 
480
 
 
481
ext_modules = []
 
482
ext_modules.append(xlib_ext)
 
483
ext_modules.append(pygtkhacks_ext)
 
484
ext_modules.append(webkitgtkhacks_ext)
 
485
 
 
486
#### Run setup ####
 
487
setup(name='miro',
 
488
    version=appVersion,
 
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/',
 
493
    scripts = [
 
494
        os.path.join(platform_dir, 'miro'),
 
495
        os.path.join(platform_dir, 'miro.real')
 
496
    ],
 
497
    data_files=data_files,
 
498
    ext_modules=ext_modules,
 
499
    packages = [
 
500
        'miro',
 
501
        'miro.dl_daemon',
 
502
        'miro.test',
 
503
        'miro.dl_daemon.private',
 
504
        'miro.frontends',
 
505
        'miro.frontends.cli',
 
506
        'miro.frontends.shell',
 
507
        'miro.frontends.widgets',
 
508
        'miro.frontends.widgets.gtk',
 
509
        'miro.plat',
 
510
        'miro.plat.frontends',
 
511
        'miro.plat.frontends.widgets',
 
512
        'miro.plat.renderers',
 
513
    ],
 
514
    package_dir = {
 
515
        'miro': portable_dir,
 
516
        'miro.test': test_dir,
 
517
        'miro.plat': platform_package_dir,
 
518
    },
 
519
    cmdclass = {
 
520
        'test_system': test_system,
 
521
        'build_ext': build_ext,
 
522
        'install_data': install_data,
 
523
        'install_theme': install_theme,
 
524
        'clean': clean,
 
525
    }
 
526
)