PageRenderTime 39ms CodeModel.GetById 14ms app.highlight 21ms RepoModel.GetById 1ms app.codeStats 0ms

/setup.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 388 lines | 361 code | 17 blank | 10 comment | 30 complexity | 13b1b93f72868d5b4c6c404abab60e97 MD5 | raw file
  1# setup.py
  2# A distutils setup script to install TortoiseHg in Windows and Posix
  3# environments.
  4#
  5# On Windows, this script is mostly used to build a stand-alone
  6# TortoiseHg package.  See installer\build.txt for details. The other
  7# use is to report the current version of the TortoiseHg source.
  8
  9
 10import time
 11import sys
 12import os
 13import shutil
 14import subprocess
 15from fnmatch import fnmatch
 16from distutils import log
 17from distutils.core import setup, Command
 18from distutils.command.build import build as _build_orig
 19from distutils.command.clean import clean as _clean_orig
 20from distutils.dep_util import newer
 21from distutils.spawn import spawn, find_executable
 22from os.path import isdir, exists, join, walk, splitext
 23
 24thgcopyright = 'Copyright (C) 2010 Steve Borho and others'
 25hgcopyright = 'Copyright (C) 2005-2010 Matt Mackall and others'
 26
 27class build_mo(Command):
 28
 29    description = "build translations (.mo files)"
 30    user_options = []
 31
 32    def initialize_options(self):
 33        pass
 34
 35    def finalize_options(self):
 36        pass
 37
 38    def run(self):
 39        if not find_executable('msgfmt'):
 40            self.warn("could not find msgfmt executable, no translations "
 41                     "will be built")
 42            return
 43
 44        podir = 'i18n/tortoisehg'
 45        if not os.path.isdir(podir):
 46            self.warn("could not find %s/ directory" % podir)
 47            return
 48
 49        join = os.path.join
 50        for po in os.listdir(podir):
 51            if not po.endswith('.po'):
 52                continue
 53            pofile = join(podir, po)
 54            modir = join('locale', po[:-3], 'LC_MESSAGES')
 55            mofile = join(modir, 'tortoisehg.mo')
 56            cmd = ['msgfmt', '-v', '-o', mofile, pofile]
 57            if sys.platform != 'sunos5':
 58                # msgfmt on Solaris does not know about -c
 59                cmd.append('-c')
 60            self.mkpath(modir)
 61            self.make_file([pofile], mofile, spawn, (cmd,))
 62
 63class build_qt(Command):
 64    description = "build PyQt GUIs (.ui) and resources (.qrc)"
 65    user_options = [('force', 'f', 'forcibly compile everything'
 66                     ' (ignore file timestamps)')]
 67    boolean_options = ('force',)
 68
 69    def initialize_options(self):
 70        self.force = None
 71
 72    def finalize_options(self):
 73        self.set_undefined_options('build', ('force', 'force'))
 74
 75    def compile_ui(self, ui_file, py_file=None):
 76        # Search for pyuic4 in python bin dir, then in the $Path.
 77        if py_file is None:
 78            py_file = splitext(ui_file)[0] + "_ui.py"
 79        if not(self.force or newer(ui_file, py_file)):
 80            return
 81        try:
 82            from PyQt4 import uic
 83            fp = open(py_file, 'w')
 84            uic.compileUi(ui_file, fp)
 85            fp.close()
 86            log.info('compiled %s into %s' % (ui_file, py_file))
 87        except Exception, e:
 88            self.warn('Unable to compile user interface %s: %s' % (py_file, e))
 89            if not exists(py_file) or not file(py_file).read():
 90                raise SystemExit(1)
 91            return
 92
 93    def compile_rc(self, qrc_file, py_file=None):
 94        # Search for pyuic4 in python bin dir, then in the $Path.
 95        if py_file is None:
 96            py_file = splitext(qrc_file)[0] + "_rc.py"
 97        if not(self.force or newer(qrc_file, py_file)):
 98            return
 99        if os.system('pyrcc4 "%s" -o "%s"' % (qrc_file, py_file)) > 0:
100            self.warn("Unable to generate python module %s for resource file %s"
101                      % (py_file, qrc_file))
102            if not exists(py_file) or not file(py_file).read():
103                raise SystemExit(1)
104        else:
105            log.info('compiled %s into %s' % (qrc_file, py_file))
106
107    def run(self):
108        self._wrapuic()
109        basepath = join(os.path.dirname(__file__), 'tortoisehg', 'hgqt')
110        for dirpath, _, filenames in os.walk(basepath):
111            for filename in filenames:
112                if filename.endswith('.ui'):
113                    self.compile_ui(join(dirpath, filename))
114                elif filename.endswith('.qrc'):
115                    self.compile_rc(join(dirpath, filename))
116
117    _wrappeduic = False
118    @classmethod
119    def _wrapuic(cls):
120        """wrap uic to use gettext's _() in place of tr()"""
121        if cls._wrappeduic:
122            return
123
124        from PyQt4.uic.Compiler import compiler, qtproxies, indenter
125
126        class _UICompiler(compiler.UICompiler):
127            def createToplevelWidget(self, classname, widgetname):
128                o = indenter.getIndenter()
129                o.level = 0
130                o.write('from tortoisehg.hgqt.i18n import _')
131                return super(_UICompiler, self).createToplevelWidget(classname, widgetname)
132        compiler.UICompiler = _UICompiler
133
134        class _i18n_string(qtproxies.i18n_string):
135            def __str__(self):
136                return "_('%s')" % self.string.encode('string-escape')
137        qtproxies.i18n_string = _i18n_string
138
139        cls._wrappeduic = True
140
141class clean_local(Command):
142    pats = ['*.py[co]', '*_ui.py', '*_rc.py', '*.orig', '*.rej']
143    excludedirs = ['.hg', 'build', 'dist']
144    description = 'clean up generated files (%s)' % ', '.join(pats)
145    user_options = []
146
147    def initialize_options(self):
148        pass
149
150    def finalize_options(self):
151        pass
152
153    def run(self):
154        for e in self._walkpaths('.'):
155            log.info("removing '%s'" % e)
156            os.remove(e)
157
158    def _walkpaths(self, path):
159        for root, _dirs, files in os.walk(path):
160            if any(root == join(path, e) or root.startswith(join(path, e, ''))
161                   for e in self.excludedirs):
162                continue
163            for e in files:
164                fpath = join(root, e)
165                if any(fnmatch(fpath, p) for p in self.pats):
166                    yield fpath
167
168class build(_build_orig):
169    sub_commands = [
170        ('build_qt', None),
171        ('build_mo', None),
172        ] + _build_orig.sub_commands
173
174class clean(_clean_orig):
175    sub_commands = [
176        ('clean_local', None),
177        ] + _clean_orig.sub_commands
178
179    def run(self):
180        _clean_orig.run(self)
181        for e in self.get_sub_commands():
182            self.run_command(e)
183
184cmdclass = {
185        'build': build,
186        'build_qt': build_qt ,
187        'build_mo': build_mo ,
188        'clean': clean,
189        'clean_local': clean_local,
190    }
191
192def setup_windows(version):
193    # Specific definitios for Windows NT-alike installations
194    _scripts = []
195    _data_files = []
196    _packages = ['tortoisehg.hgqt', 'tortoisehg.util', 'tortoisehg']
197    extra = {}
198    hgextmods = []
199
200    # py2exe needs to be installed to work
201    try:
202        import py2exe
203
204        # Help py2exe to find win32com.shell
205        try:
206            import modulefinder
207            import win32com
208            for p in win32com.__path__[1:]: # Take the path to win32comext
209                modulefinder.AddPackagePath("win32com", p)
210            pn = "win32com.shell"
211            __import__(pn)
212            m = sys.modules[pn]
213            for p in m.__path__[1:]:
214                modulefinder.AddPackagePath(pn, p)
215        except ImportError:
216            pass
217
218    except ImportError:
219        if '--version' not in sys.argv:
220            raise
221
222    if 'py2exe' in sys.argv:
223        import hgext
224        hgextdir = os.path.dirname(hgext.__file__)
225        hgextmods = set(["hgext." + os.path.splitext(f)[0]
226                      for f in os.listdir(hgextdir)])
227        _data_files = [(root, [os.path.join(root, file_) for file_ in files])
228                            for root, dirs, files in os.walk('icons')]
229
230    # for PyQt, see http://www.py2exe.org/index.cgi/Py2exeAndPyQt
231    includes = ['sip']
232
233    # Qt4 plugins, see http://stackoverflow.com/questions/2206406/
234    def qt4_plugins(subdir, *dlls):
235        import PyQt4
236        pluginsdir = join(os.path.dirname(PyQt4.__file__), 'plugins')
237        return (subdir, [join(pluginsdir, subdir, e) for e in dlls])
238    _data_files.append(qt4_plugins('imageformats', 'qico4.dll', 'qsvg4.dll'))
239
240    # Manually include other modules py2exe can't find by itself.
241    if 'hgext.highlight' in hgextmods:
242        includes += ['pygments.*', 'pygments.lexers.*', 'pygments.formatters.*',
243                     'pygments.filters.*', 'pygments.styles.*']
244    if 'hgext.patchbomb' in hgextmods:
245        includes += ['email.*', 'email.mime.*']
246
247    extra['options'] = {
248       "py2exe" : {
249           "skip_archive" : 0,
250
251           # Don't pull in all this MFC stuff used by the makepy UI.
252           "excludes" : "pywin,pywin.dialogs,pywin.dialogs.list"
253                        ",setup,distutils",  # required only for in-place use
254           "includes" : includes,
255           "optimize" : 1
256       }
257    }
258    shutil.copyfile('thg', 'thgw')
259    extra['console'] = [
260            {'script':'thg',
261             'icon_resources':[(0,'icons/thg_logo.ico')],
262             'description':'TortoiseHg GUI tools for Mercurial SCM',
263             'copyright':thgcopyright,
264             'product_version':version},
265            {'script':'contrib/hg', 
266             'icon_resources':[(0,'icons/hg.ico')],
267             'description':'Mercurial Distributed SCM',
268             'copyright':hgcopyright,
269             'product_version':version},
270            {'script':'win32/docdiff.py',
271             'icon_resources':[(0,'icons/TortoiseMerge.ico')],
272             'copyright':thgcopyright,
273             'product_version':version}
274            ]
275    extra['windows'] = [
276            {'script':'thgw',
277             'icon_resources':[(0,'icons/thg_logo.ico')],
278             'description':'TortoiseHg GUI tools for Mercurial SCM',
279             'copyright':thgcopyright,
280             'product_version':version},
281            {'script':'TortoiseHgOverlayServer.py',
282             'icon_resources':[(0,'icons/thg_logo.ico')],
283             'description':'TortoiseHg Overlay Icon Server',
284             'copyright':thgcopyright,
285             'product_version':version}
286            ]
287
288    return _scripts, _packages, _data_files, extra
289
290
291def setup_posix():
292    # Specific definitios for Posix installations
293    _extra = {}
294    _scripts = ['thg']
295    _packages = ['tortoisehg', 'tortoisehg.hgqt', 'tortoisehg.util']
296    _data_files = [(os.path.join('share/pixmaps/tortoisehg', root),
297        [os.path.join(root, file_) for file_ in files])
298        for root, dirs, files in os.walk('icons')]
299    _data_files += [(os.path.join('share', root),
300        [os.path.join(root, file_) for file_ in files])
301        for root, dirs, files in os.walk('locale')]
302    _data_files += [('lib/nautilus/extensions-2.0/python',
303                     ['contrib/nautilus-thg.py'])]
304
305    # Create a config.py.  Distributions will need to supply their own
306    cfgfile = os.path.join('tortoisehg', 'util', 'config.py')
307    if not os.path.exists(cfgfile) and not os.path.exists('.hg/requires'):
308        f = open(cfgfile, "w")
309        f.write('bin_path     = "/usr/bin"\n')
310        f.write('license_path = "/usr/share/doc/tortoisehg/Copying.txt.gz"\n')
311        f.write('locale_path  = "/usr/share/locale"\n')
312        f.write('icon_path    = "/usr/share/pixmaps/tortoisehg/icons"\n')
313        f.write('nofork       = True\n')
314        f.close()
315
316    return _scripts, _packages, _data_files, _extra
317
318def runcmd(cmd, env):
319    p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
320                         stderr=subprocess.PIPE, env=env)
321    out, err = p.communicate()
322    # If root is executing setup.py, but the repository is owned by
323    # another user (as in "sudo python setup.py install") we will get
324    # trust warnings since the .hg/hgrc file is untrusted. That is
325    # fine, we don't want to load it anyway.
326    err = [e for e in err.splitlines()
327           if not e.startswith('Not trusting file')]
328    if err:
329        return ''
330    return out
331
332if __name__ == '__main__':
333    version = ''
334
335    if os.path.isdir('.hg'):
336        from tortoisehg.util import version as _version
337        branch, version = _version.liveversion()
338        if version.endswith('+'):
339            version += time.strftime('%Y%m%d')
340    elif os.path.exists('.hg_archival.txt'):
341        kw = dict([t.strip() for t in l.split(':', 1)]
342                  for l in open('.hg_archival.txt'))
343        if 'tag' in kw:
344            version =  kw['tag']
345        elif 'latesttag' in kw:
346            version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
347        else:
348            version = kw.get('node', '')[:12]
349
350    if version:
351        f = open("tortoisehg/util/__version__.py", "w")
352        f.write('# this file is autogenerated by setup.py\n')
353        f.write('version = "%s"\n' % version)
354        f.close()
355
356    try:
357        import tortoisehg.util.__version__
358        version = tortoisehg.util.__version__.version
359    except ImportError:
360        version = 'unknown'
361
362    if os.name == "nt":
363        (scripts, packages, data_files, extra) = setup_windows(version)
364        desc = 'Windows shell extension for Mercurial VCS'
365        # Windows binary file versions for exe/dll files must have the
366        # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
367        from tortoisehg.util.version import package_version
368        setupversion = package_version()
369        productname = 'TortoiseHg'
370    else:
371        (scripts, packages, data_files, extra) = setup_posix()
372        desc = 'TortoiseHg dialogs for Mercurial VCS'
373        setupversion = version
374        productname = 'tortoisehg'
375
376    setup(name=productname,
377            version=setupversion,
378            author='Steve Borho',
379            author_email='steve@borho.org',
380            url='http://tortoisehg.org',
381            description=desc,
382            license='GNU GPL2',
383            scripts=scripts,
384            packages=packages,
385            data_files=data_files,
386            cmdclass=cmdclass,
387            **extra
388        )