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 9import time 10import sys 11import os 12import shutil 13import tempfile 14import re 15import subprocess 16import tarfile 17from fnmatch import fnmatch 18from distutils import log 19if 'FORCE_SETUPTOOLS' in os.environ: 20 from setuptools import setup 21else: 22 if 'py2app' in sys.argv[1:]: 23 sys.exit("py2app requires FORCE_SETUPTOOLS=1 to be set in os.environ.") 24 25 from distutils.core import setup 26from distutils.core import Command 27from distutils.command.build import build as _build_orig 28from distutils.command.clean import clean as _clean_orig 29from distutils.spawn import spawn, find_executable 30from i18n.msgfmt import Msgfmt 31 32thgcopyright = 'Copyright (C) 2010-2021 Steve Borho and others' 33hgcopyright = 'Copyright (C) 2005-2021 Matt Mackall and others' 34 35if sys.version_info[0] >= 3: 36 unicode = str # pycompat.unicode 37 38def _walklocales(): 39 podir = 'i18n/tortoisehg' 40 for po in os.listdir(podir): 41 if not po.endswith('.po'): 42 continue 43 pofile = os.path.join(podir, po) 44 modir = os.path.join('locale', po[:-3], 'LC_MESSAGES') 45 mofile = os.path.join(modir, 'tortoisehg.mo') 46 yield pofile, modir, mofile 47 48def _msgfmt(pofile, mofile): 49 modata = Msgfmt(pofile).get() 50 with open(mofile, "wb") as fp: 51 fp.write(modata) 52 53class build_mo(Command): 54 description = "build translations (.mo files)" 55 user_options = [] 56 57 def initialize_options(self): 58 pass 59 60 def finalize_options(self): 61 pass 62 63 def run(self): 64 for pofile, modir, mofile in _walklocales(): 65 self.mkpath(modir) 66 self.make_file(pofile, mofile, _msgfmt, (pofile, mofile)) 67 68class import_po(Command): 69 description = "import translations (.po file)" 70 user_options = [ 71 ("package=", "p", ("launchpad export package or bzr repo " 72 "[default: launchpad-export.tar.gz]")), 73 ("lang=", "l", "languages to be imported, separated by ','"), 74 ] 75 76 def initialize_options(self): 77 self.package = None 78 self.lang = None 79 80 def finalize_options(self): 81 if not self.package: 82 self.package = 'launchpad-export.tar.gz' 83 84 if self.lang: 85 self.lang = self.lang.upper().split(',') 86 87 def _untar(self, name, path='.'): 88 with tarfile.open(name, 'r') as tar: 89 path = os.path.abspath(path) 90 for tarinfo in tar.getmembers(): 91 # Extract the safe file only 92 p = os.path.abspath(os.path.join(path, tarinfo.name)) 93 if p.startswith(path): 94 tar.extract(tarinfo, path) 95 96 def run(self): 97 if not find_executable('msgcat'): 98 self.warn("could not find msgcat executable") 99 return 100 101 dest_prefix = 'i18n/tortoisehg' 102 src_prefix = 'po/tortoisehg' 103 104 log.info('import from %s' % self.package) 105 106 if os.path.isdir(self.package): 107 self.bzrrepo = True 108 self.package_path = self.package 109 elif tarfile.is_tarfile(self.package): 110 self.bzrrepo = False 111 self.package_path = tempfile.mkdtemp() 112 self._untar(self.package, self.package_path) 113 else: 114 self.warn('%s is not a valid tranlation package' % self.package) 115 return 116 117 if self.bzrrepo: 118 filter = r'^([\S]+)\.po$' 119 else: 120 filter = r'^tortoisehg-([\S]+)\.po$' 121 r = re.compile(filter) 122 123 src_dir = os.path.join(self.package_path, src_prefix) 124 for src_file in os.listdir(src_dir): 125 m = r.match(src_file) 126 if not m: 127 continue 128 129 # filter the language 130 lang = m.group(1) 131 if self.lang and lang.upper() not in self.lang: 132 continue 133 134 dest_file = os.path.join(dest_prefix, lang) + '.po' 135 msg = 'updating %s...' % dest_file 136 cmd = ['msgcat', 137 '--no-location', 138 '-o', dest_file, 139 os.path.join(src_dir, src_file) 140 ] 141 self.execute(spawn, (cmd,), msg) 142 143 if not self.bzrrepo: 144 shutil.rmtree(self.package_path) 145 146class update_pot(Command): 147 description = "extract translatable strings to tortoisehg.pot" 148 user_options = [] 149 150 def initialize_options(self): 151 pass 152 153 def finalize_options(self): 154 pass 155 156 def run(self): 157 if not find_executable('xgettext'): 158 self.warn("could not find xgettext executable, tortoisehg.pot " 159 "won't be built") 160 return 161 162 dirlist = [ 163 '.', 164 'contrib', 165 'contrib/win32', 166 'tortoisehg', 167 'tortoisehg/hgqt', 168 'tortoisehg/util', 169 'tortoisehg/thgutil/iniparse', 170 ] 171 172 filelist = [] 173 for pathname in dirlist: 174 if not os.path.exists(pathname): 175 continue 176 for filename in os.listdir(pathname): 177 if filename.endswith('.py'): 178 filelist.append(os.path.join(pathname, filename)) 179 filelist.sort() 180 181 potfile = 'tortoisehg.pot' 182 183 cmd = [ 184 'xgettext', 185 '--package-name', 'TortoiseHg', 186 '--msgid-bugs-address', '<thg-devel@googlegroups.com>', 187 '--copyright-holder', thgcopyright, 188 '--from-code', 'ISO-8859-1', 189 '--keyword=_:1,2c,2t', 190 '--add-comments=i18n:', 191 '-d', '.', 192 '-o', potfile, 193 ] 194 cmd += filelist 195 self.make_file(filelist, potfile, spawn, (cmd,)) 196 197class build_config(Command): 198 description = 'create config module for unix installation' 199 user_options = [ 200 ('build-lib=', 'd', 'directory to "build" (copy) to'), 201 ] 202 203 def initialize_options(self): 204 self.build_lib = None 205 206 def finalize_options(self): 207 self.set_undefined_options('build', 208 ('build_lib', 'build_lib')) 209 210 def _generate_config(self, cfgfile): 211 from tortoisehg.hgqt import qtcore 212 # dirty hack to get the install root 213 installcmd = self.get_finalized_command('install') 214 rootlen = len(installcmd.root or '') 215 sharedir = os.path.join(installcmd.install_data[rootlen:], 'share') 216 data = { 217 'bin_path': installcmd.install_scripts[rootlen:], 218 'license_path': os.path.join(sharedir, 'doc', 'tortoisehg', 219 'COPYING.txt'), 220 'locale_path': os.path.join(sharedir, 'locale'), 221 'icon_path': os.path.join(sharedir, 'pixmaps', 'tortoisehg'), 222 'nofork': True, 223 'qt_api': qtcore._detectapi(), 224 } 225 # Distributions will need to supply their own 226 with open(cfgfile, "w") as f: 227 for k, v in sorted(data.items()): 228 f.write('%s = %r\n' % (k, v)) 229 230 def run(self): 231 cfgdir = os.path.join(self.build_lib, 'tortoisehg', 'util') 232 cfgfile = os.path.join(cfgdir, 'config.py') 233 self.mkpath(cfgdir) 234 self.make_file(__file__, cfgfile, self._generate_config, (cfgfile,)) 235 236class build_py2app_config(build_config): 237 description = 'create config module for standalone OS X bundle' 238 239 def _generate_config(self, cfgfile): 240 from tortoisehg.hgqt import qtcore 241 # Since py2app seems to ignore the build dir in favor of the src tree, 242 # ignore the given path and generate it in the source tree. The file 243 # is conditionalized such that it won't interfere when run from source. 244 cwd = os.path.dirname(__file__) 245 cfgfile = os.path.join(cwd, 'tortoisehg', 'util', 'config.py') 246 data = { 247 'license_path': 'COPYING.txt', 248 'locale_path': 'locale', 249 'icon_path': 'icons', 250 'qt_api': qtcore._detectapi(), 251 } 252 rsrc_dir = 'os.environ["RESOURCEPATH"]' 253 254 with open(cfgfile, "w") as f: 255 f.write("import os, sys\n" 256 "\n" 257 "if 'THG_OSX_APP' in os.environ:\n" 258 " nofork = True\n") 259 for k, v in sorted(data.items()): 260 f.write(" %s = os.path.join(%s, '%s')\n" % (k, rsrc_dir, v)) 261 f.write(" bin_path = os.path.dirname(sys.executable)\n") 262 263class build_ui(Command): 264 description = 'build PyQt user interfaces (.ui)' 265 user_options = [ 266 ('force', 'f', 'forcibly compile everything (ignore file timestamps)'), 267 ] 268 boolean_options = ('force',) 269 270 def initialize_options(self): 271 self.force = None 272 273 def finalize_options(self): 274 self.set_undefined_options('build', ('force', 'force')) 275 276 def _compile_ui(self, ui_file, py_file): 277 uic = self._impuic() 278 with open(py_file, 'w') as fp: 279 uic.compileUi(ui_file, fp) 280 281 @staticmethod 282 def _impuic(): 283 from tortoisehg.hgqt.qtcore import QT_API 284 mod = __import__(QT_API, globals(), locals(), ['uic']) 285 return mod.uic 286 287 _wrappeduic = False 288 @classmethod 289 def _wrapuic(cls): 290 """wrap uic to use gettext's _() in place of 291 QtGui.QApplication.translate as _translate()""" 292 if cls._wrappeduic: 293 return 294 295 uic = cls._impuic() 296 compiler = uic.Compiler.compiler 297 qtproxies = uic.Compiler.qtproxies 298 indenter = uic.Compiler.indenter 299 300 class _UICompiler(compiler.UICompiler): 301 def createToplevelWidget(self, classname, widgetname): 302 o = indenter.getIndenter() 303 o.level = 0 304 o.write('from tortoisehg.util.i18n import _') 305 return super(_UICompiler, self).createToplevelWidget(classname, 306 widgetname) 307 compiler.UICompiler = _UICompiler 308 309 class _i18n_string(qtproxies.i18n_string): 310 def __str__(self): 311 # Note: ignoring self.disambig and qtproxies.i18n_context 312 return '_(%s)' % qtproxies.as_string(self.string) 313 qtproxies.i18n_string = _i18n_string 314 315 cls._wrappeduic = True 316 317 def run(self): 318 self._wrapuic() 319 basepath = os.path.join(os.path.dirname(__file__), 'tortoisehg', 'hgqt') 320 for f in os.listdir(basepath): 321 if not f.endswith('.ui'): 322 continue 323 uifile = os.path.join(basepath, f) 324 pyfile = uifile[:-3] + '_ui.py' 325 # setup.py is the source of "from i18n import _" line 326 self.make_file([uifile, __file__], pyfile, 327 self._compile_ui, (uifile, pyfile)) 328 329class build_qrc(Command): 330 description = 'build PyQt resource files (.qrc)' 331 user_options = [ 332 ('build-lib=', 'd', 'directory to "build" (copy) to'), 333 ('force', 'f', 'forcibly compile everything (ignore file timestamps)'), 334 ] 335 boolean_options = ('force',) 336 337 def initialize_options(self): 338 self.build_lib = None 339 self.force = None 340 341 def finalize_options(self): 342 self.set_undefined_options('build', 343 ('build_lib', 'build_lib'), 344 ('force', 'force')) 345 346 def _findrcc(self): 347 from tortoisehg.hgqt.qtcore import QT_API 348 try: 349 rcc = {'PyQt4': 'pyrcc4', 'PyQt5': 'pyrcc5'}[QT_API] 350 except KeyError: 351 raise RuntimeError('unsupported Qt API: %s' % QT_API) 352 if os.name != 'nt' or QT_API == 'PyQt5': 353 return rcc 354 mod = __import__(QT_API, globals(), locals()) 355 return os.path.join(os.path.dirname(mod.__file__), rcc) 356 357 def _generate_qrc(self, qrc_file, srcfiles, prefix): 358 from tortoisehg.hgqt import qtlib 359 basedir = os.path.dirname(qrc_file) 360 with open(qrc_file, 'w') as f: 361 f.write('<!DOCTYPE RCC><RCC version="1.0">\n') 362 f.write(' <qresource prefix="%s">\n' % qtlib.htmlescape(prefix)) 363 for e in srcfiles: 364 relpath = e[len(basedir) + 1:] 365 f.write(' <file>%s</file>\n' 366 % qtlib.htmlescape(relpath.replace(os.path.sep, '/'), 367 False)) 368 f.write(' </qresource>\n') 369 f.write('</RCC>\n') 370 371 def _build_rc(self, srcfiles, py_file, basedir, prefix): 372 """Generate compiled resource including any files under basedir""" 373 # For details, see http://doc.qt.nokia.com/latest/resources.html 374 qrc_file = os.path.join(basedir, '%s.qrc' % os.path.basename(basedir)) 375 try: 376 self._generate_qrc(qrc_file, srcfiles, prefix) 377 env = os.environ.copy() 378 if os.name == 'nt' and 'VIRTUAL_ENV' in env: 379 # pyrcc5.bat gets installed to the root of the virtualenv, not 380 # in the Scripts directory with the binaries on PATH. 381 env['PATH'] = '%s%s%s' % ( 382 env.get('PATH'), os.pathsep, env.get('VIRTUAL_ENV') 383 ) 384 subprocess.check_call( 385 [self._findrcc(), qrc_file, '-o', py_file], 386 env=env, 387 shell=os.name == 'nt' 388 ) 389 finally: 390 os.unlink(qrc_file) 391 392 def _build_icons(self, basepath): 393 icondir = os.path.join(os.path.dirname(__file__), 'icons') 394 iconfiles = [] 395 for root, dirs, files in os.walk(icondir): 396 if root == icondir: 397 dirs.remove('svg') # drop source of .ico files 398 iconfiles.extend(os.path.join(root, f) for f in files 399 if f.endswith(('.png', '.svg'))) 400 pyfile = os.path.join(basepath, 'icons_rc.py') 401 # we cannot detect deleted icons 402 self.make_file(iconfiles, pyfile, 403 self._build_rc, (iconfiles, pyfile, icondir, '/icons'), 404 exec_msg='generating %s from %s' % (pyfile, icondir)) 405 406 def _build_translations(self, basepath): 407 """Build translations_rc.py which inclues qt_xx.qm""" 408 from tortoisehg.hgqt.qtcore import QT_API 409 if QT_API == 'PyQt4': 410 if os.name == 'nt': 411 import PyQt4 412 trpath = os.path.join( 413 os.path.dirname(PyQt4.__file__), 'translations') 414 else: 415 from PyQt4.QtCore import QLibraryInfo 416 trpath = unicode( 417 QLibraryInfo.location(QLibraryInfo.TranslationsPath)) 418 else: 419 if os.name == 'nt': 420 import PyQt5 421 trpath = os.path.join( 422 os.path.dirname(PyQt5.__file__), 'translations') 423 else: 424 from PyQt5.QtCore import QLibraryInfo 425 trpath = unicode( 426 QLibraryInfo.location(QLibraryInfo.TranslationsPath)) 427 builddir = os.path.join(self.get_finalized_command('build').build_base, 428 'qt-translations') 429 self.mkpath(builddir) 430 431 # we have to copy .qm files to build directory because .qrc file must 432 # specify files by relative paths 433 qmfiles = [] 434 for e in os.listdir(trpath): 435 if (not e.startswith(('qt_', 'qscintilla_')) 436 or e.startswith('qt_help_') 437 or not e.endswith('.qm')): 438 continue 439 f = os.path.join(builddir, e) 440 self.copy_file(os.path.join(trpath, e), f) 441 qmfiles.append(f) 442 pyfile = os.path.join(basepath, 'translations_rc.py') 443 self.make_file(qmfiles, pyfile, self._build_rc, 444 (qmfiles, pyfile, builddir, '/translations'), 445 exec_msg='generating %s from Qt translation' % pyfile) 446 447 def run(self): 448 basepath = os.path.join(self.build_lib, 'tortoisehg', 'hgqt') 449 self.mkpath(basepath) 450 self._build_icons(basepath) 451 self._build_translations(basepath) 452 453class clean_local(Command): 454 pats = ['*.py[co]', '*_ui.py', '*.mo', '*.orig', '*.rej'] 455 excludedirs = ['.hg', 'build', 'dist'] 456 description = 'clean up generated files (%s)' % ', '.join(pats) 457 user_options = [] 458 459 def initialize_options(self): 460 pass 461 462 def finalize_options(self): 463 pass 464 465 def run(self): 466 for e in self._walkpaths('.'): 467 log.info("removing '%s'" % e) 468 os.remove(e) 469 470 def _walkpaths(self, path): 471 for root, _dirs, files in os.walk(path): 472 if any(root == os.path.join(path, e) 473 or root.startswith(os.path.join(path, e, '')) 474 for e in self.excludedirs): 475 continue 476 for e in files: 477 fpath = os.path.join(root, e) 478 if any(fnmatch(fpath, p) for p in self.pats): 479 yield fpath 480 481class build(_build_orig): 482 sub_commands = [ 483 ('build_config', 484 lambda self: (os.name != 'nt' and 485 'py2app' not in self.distribution.commands)), 486 ('build_py2app_config', 487 lambda self: 'py2app' in self.distribution.commands), 488 ('build_ui', None), 489 ('build_qrc', lambda self: 'py2exe' in self.distribution.commands), 490 ('build_mo', None), 491 ] + _build_orig.sub_commands 492 493class clean(_clean_orig): 494 sub_commands = [ 495 ('clean_local', None), 496 ] + _clean_orig.sub_commands 497 498 def run(self): 499 _clean_orig.run(self) 500 for e in self.get_sub_commands(): 501 self.run_command(e) 502 503cmdclass = { 504 'build': build, 505 'build_config': build_config, 506 'build_py2app_config': build_py2app_config, 507 'build_ui': build_ui, 508 'build_qrc': build_qrc, 509 'build_mo': build_mo, 510 'clean': clean, 511 'clean_local': clean_local, 512 'update_pot': update_pot, 513 'import_po': import_po, 514 } 515 516def setup_windows(version): 517 # Specific definitios for Windows NT-alike installations 518 _scripts = [] 519 _data_files = [] 520 _packages = ['tortoisehg.hgqt', 'tortoisehg.util', 'tortoisehg', 'hgext3rd'] 521 extra = {} 522 hgextmods = [] 523 524 # py2exe needs to be installed to work 525 try: 526 import py2exe 527 528 # Help py2exe to find win32com.shell 529 try: 530 import modulefinder 531 import win32com 532 for p in win32com.__path__[1:]: # Take the path to win32comext 533 modulefinder.AddPackagePath("win32com", p) 534 pn = "win32com.shell" 535 __import__(pn) 536 m = sys.modules[pn] 537 for p in m.__path__[1:]: 538 modulefinder.AddPackagePath(pn, p) 539 except ImportError: 540 pass 541 542 except ImportError: 543 if '--version' not in sys.argv: 544 raise 545 546 # Allow use of environment variables to specify the location of Mercurial 547 import modulefinder 548 path = os.getenv('MERCURIAL_PATH') 549 if path: 550 modulefinder.AddPackagePath('mercurial', path) 551 path = os.getenv('HGEXT_PATH') 552 if path: 553 modulefinder.AddPackagePath('hgext', path) 554 modulefinder.AddPackagePath('hgext3rd', 'hgext3rd') 555 556 if 'py2exe' in sys.argv: 557 import hgext 558 hgextdir = os.path.dirname(hgext.__file__) 559 hgextmods = set(["hgext." + os.path.splitext(f)[0] 560 for f in os.listdir(hgextdir)]) 561 # most icons are packed into Qt resource, but .ico files must reside 562 # in filesystem so that shell extension can read them 563 root = 'icons' 564 _data_files.append((root, 565 [os.path.join(root, f) for f in os.listdir(root) 566 if f.endswith('.ico') or f == 'README.txt'])) 567 568 # for PyQt, see http://www.py2exe.org/index.cgi/Py2exeAndPyQt 569 includes = ['PyQt5.sip'] 570 571 # Qt4 plugins, see http://stackoverflow.com/questions/2206406/ 572 def qt4_plugins(subdir, *dlls): 573 import PyQt4 574 pluginsdir = os.path.join(os.path.dirname(PyQt4.__file__), 'plugins') 575 return subdir, [os.path.join(pluginsdir, subdir, e) for e in dlls] 576 577 def qt5_plugins(subdir, *dlls): 578 import PyQt5 579 pluginsdir = os.path.join(os.path.dirname(PyQt5.__file__), 'plugins') 580 return subdir, [os.path.join(pluginsdir, subdir, e) for e in dlls] 581 582 from tortoisehg.hgqt.qtcore import QT_API 583 if QT_API == 'PyQt4': 584 _data_files.append(qt4_plugins('imageformats', 585 'qico4.dll', 'qsvg4.dll')) 586 else: 587 _data_files.append(qt5_plugins('platforms', 'qwindows.dll')) 588 _data_files.append(qt5_plugins('imageformats', 589 'qico.dll', 'qsvg.dll', 'qjpeg.dll', 590 'qgif.dll', 'qicns.dll', 'qtga.dll', 591 'qtiff.dll', 'qwbmp.dll', 'qwebp.dll')) 592 593 # Manually include other modules py2exe can't find by itself. 594 if 'hgext.highlight' in hgextmods: 595 includes += ['pygments.*', 'pygments.lexers.*', 'pygments.formatters.*', 596 'pygments.filters.*', 'pygments.styles.*'] 597 if 'hgext.patchbomb' in hgextmods: 598 includes += ['email.*', 'email.mime.*'] 599 600 extra['options'] = {} 601 extra['options']['py2exe'] = { 602 "skip_archive": 0, 603 # Don't pull in all this MFC stuff used by the makepy UI. 604 "excludes": ("pywin,pywin.dialogs,pywin.dialogs.list,setuptools" 605 "setup,distutils"), # required only for in-place use 606 "includes": includes, 607 "optimize": 1, 608 } 609 extra['console'] = [ 610 {'script': 'thg', 611 'icon_resources': [(0, 'icons/thg_logo.ico')], 612 'description': 'TortoiseHg GUI tools for Mercurial SCM', 613 'copyright': thgcopyright, 614 'product_version': version, 615 }, 616 {'script': 'contrib/hg', 617 'icon_resources': [(0, 'icons/hg.ico')], 618 'description': 'Mercurial Distributed SCM', 619 'copyright': hgcopyright, 620 'product_version': version, 621 }, 622 {'script': 'win32/docdiff.py', 623 'icon_resources': [(0, 'icons/TortoiseMerge.ico')], 624 'copyright': thgcopyright, 625 'product_version': version, 626 }, 627 ] 628 extra['windows'] = [ 629 {'script': 'thg', 630 'dest_base': 'thgw', 631 'icon_resources': [(0, 'icons/thg_logo.ico')], 632 'description': 'TortoiseHg GUI tools for Mercurial SCM', 633 'copyright': thgcopyright, 634 'product_version': version, 635 }, 636 {'script': 'TortoiseHgOverlayServer.py', 637 'icon_resources': [(0, 'icons/thg_logo.ico')], 638 'description': 'TortoiseHg Overlay Icon Server', 639 'copyright': thgcopyright, 640 'product_version': version, 641 }, 642 ] 643 # put dlls in sub directory so that they won't pollute PATH 644 extra['zipfile'] = 'lib/library.zip' 645 646 return _scripts, _packages, _data_files, extra 647 648def setup_osx(version): 649 _extra = {} 650 651 # This causes py2app to copy the scripts into build/ and then adjust the 652 # mode, but the build dir is ignored for some reason. 653 _scripts = ['thg'] 654 655 _packages = ['tortoisehg.hgqt', 'tortoisehg.util', 'tortoisehg', 'hgext3rd'] 656 _data_files = [] 657 658 def qt5_plugins(subdir, *dlls): 659 import PyQt5 660 pluginsdir = os.path.join(os.path.dirname(PyQt5.__file__), 'plugins') 661 return subdir, [os.path.join(pluginsdir, subdir, e) for e in dlls] 662 663 from tortoisehg.hgqt.qtcore import QT_API 664 _data_files.append(qt5_plugins('platforms', 'libqcocoa.dylib')) 665 _data_files.append(qt5_plugins('imageformats', 'libqsvg.dylib')) 666 667 _py2app_options = { 668 'arch': 'x86_64', 669 'argv_emulation': False, 670 'no_chdir': True, 671 'excludes': ['Carbon', 'curses', 'distools', 'distutils', 'docutils', 672 'PyQt4.phonon', 'PyQt4.QtDeclarative', 'PyQt4.QtDesigner', 673 'PyQt4.QtHelp', 'PyQt4.QtMultimedia', 'PyQt4.QtOpenGL', 674 'PyQt4.QtScript', 'PyQt4.QtScriptTools', 'PyQt4.QtSql', 675 'PyQt4.QtTest', 'PyQt4.QtWebKit', 'PyQt4.QtXmlPatterns', 676 'PyQt4.QtXmlPatterns', 'PyQt5.QtDBus', 677 'PyQt5.QtDeclarative', 'PyQt5.QtDesigner', 'PyQt5.QtHelp', 678 'PyQt5.QtMultimedia', 'PyQt5.QtOpenGL', 'PyQt5.QtScript', 679 'PyQt5.QtScriptTools', 'PyQt5.QtSql', 'PyQt5.QtTest', 680 'PyQt5.QtWebKit', 'PyQt5.QtXmlPatterns', 'PyQt5.phonon', 681 'py2app', 'setup', 'setuptools', 'unittest', 'PIL'], 682 683 'extra_scripts': ['contrib/hg'], 684 'iconfile': 'contrib/packaging/macos/TortoiseHg.icns', 685 'includes': ['email.mime.text', 'sip'], 686 'packages': ['certifi', 'hgext', 'iniparse', 'keyring', 'mercurial', 687 'pygments', 'tortoisehg'], 688 689 'plist': { 690 'CFBundleDisplayName': 'TortoiseHg', 691 'CFBundleExecutable': 'TortoiseHg', 692 'CFBundleIdentifier': 'org.tortoisehg.thg', 693 'CFBundleName': 'TortoiseHg', 694 'CFBundleShortVersionString': version, 695 'CFBundleVersion': version, 696 'LSEnvironment': { 697 # because launched app can't inherit environment variables from 698 # console, the encoding would be set to "ascii" by default 699 'HGENCODING': 'utf-8', 700 'THG_OSX_APP': '1', 701 'QT_API': QT_API, 702 }, 703 'NSHumanReadableCopyright': thgcopyright, 704 }, 705 706 'resources': ['COPYING.txt', 'icons', 'locale'], 707 } 708 709 _extra['app'] = ['thg'] 710 _extra['setup_requires'] = ['py2app'] 711 _extra['options'] = {'py2app': _py2app_options} 712 713 return _scripts, _packages, _data_files, _extra 714 715def setup_posix(): 716 # Specific definitios for Posix installations 717 _extra = {} 718 _scripts = ['thg'] 719 _packages = ['tortoisehg', 'tortoisehg.hgqt', 'tortoisehg.util', 'hgext3rd'] 720 _data_files = [] 721 # .svg and .png are loaded by thg, .ico by nautilus extension 722 for root, dirs, files in os.walk('icons'): 723 if root == 'icons': 724 dirs.remove('svg') # drop source of .ico files 725 # do not add directories to the packing list 726 if not files: 727 continue 728 _data_files.append((os.path.join('share/pixmaps/tortoisehg', root[6:]), 729 [os.path.join(root, f) for f in files])) 730 # install SVG icon for the desktop file 731 _data_files.append(('share/pixmaps', ['icons/svg/thg_logo.svg'])) 732 _data_files.append(('share/doc/tortoisehg', ['COPYING.txt'])) 733 _data_files.extend((os.path.join('share', modir), [mofile]) 734 for pofile, modir, mofile in _walklocales()) 735# _data_files += [('share/nautilus-python/extensions', 736# ['contrib/nautilus-thg.py'])] 737 738 return _scripts, _packages, _data_files, _extra 739 740if __name__ == '__main__': 741 version = '' 742 743 if os.path.isdir('.hg'): 744 from tortoisehg.util import version as _version 745 branch, version = _version.liveversion() 746 if version.endswith('+'): 747 version += time.strftime('%Y%m%d') 748 elif os.path.exists('.hg_archival.txt'): 749 with open('.hg_archival.txt') as fp: 750 kw = dict([t.strip() for t in l.split(':', 1)] for l in fp) 751 if 'tag' in kw: 752 version = kw['tag'] 753 elif 'latesttag' in kw: 754 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw 755 else: 756 version = kw.get('node', '')[:12] 757 758 if version: 759 with open("tortoisehg/util/__version__.py", "w") as f: 760 f.write('# this file is autogenerated by setup.py\n') 761 f.write('version = "%s"\n' % version) 762 763 try: 764 import tortoisehg.util.__version__ 765 version = tortoisehg.util.__version__.version 766 except ImportError: 767 version = 'unknown' 768 769 if os.name == "nt": 770 (scripts, packages, data_files, extra) = setup_windows(version) 771 # Windows binary file versions for exe/dll files must have the 772 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535 773 from tortoisehg.util.version import package_version 774 setupversion = package_version() 775 productname = 'TortoiseHg' 776 elif sys.platform == "darwin" and 'py2app' in sys.argv[1:]: 777 (scripts, packages, data_files, extra) = setup_osx(version) 778 setupversion = version 779 productname = 'TortoiseHg' 780 else: 781 (scripts, packages, data_files, extra) = setup_posix() 782 setupversion = version 783 productname = 'tortoisehg' 784 785 setup(name=productname, 786 version=setupversion, 787 author='Steve Borho', 788 author_email='steve@borho.org', 789 url='https://tortoisehg.bitbucket.io', 790 description='TortoiseHg dialogs for Mercurial VCS', 791 license='GNU GPL2', 792 scripts=scripts, 793 packages=packages, 794 data_files=data_files, 795 cmdclass=cmdclass, 796 **extra) 797