# setup.py
# A distutils setup script to install TortoiseHg in Windows and Posix
# environments.
#
# On Windows, this script is mostly used to build a stand-alone
# TortoiseHg package.  See installer\build.txt for details. The other
# use is to report the current version of the TortoiseHg source.

from __future__ import annotations

import time
import sys
import os
import shutil
import tempfile
import re
import subprocess
import tarfile
from fnmatch import fnmatch
from distutils import log
if 'FORCE_SETUPTOOLS' in os.environ:
    from setuptools import setup
else:
    if 'py2app' in sys.argv[1:]:
        sys.exit("py2app requires FORCE_SETUPTOOLS=1 to be set in os.environ.")

    from distutils.core import setup
from distutils.core import Command
from distutils.command.build import build as _build_orig
from distutils.command.clean import clean as _clean_orig
from distutils.spawn import spawn, find_executable
from i18n.msgfmt import Msgfmt

thgcopyright = 'Copyright (C) 2010-2025 Steve Borho and others'
hgcopyright = 'Copyright (C) 2005-2025 Matt Mackall and others'


def _walklocales():
    podir = 'i18n/tortoisehg'
    for po in os.listdir(podir):
        if not po.endswith('.po'):
            continue
        pofile = os.path.join(podir, po)
        modir = os.path.join('locale', po[:-3], 'LC_MESSAGES')
        mofile = os.path.join(modir, 'tortoisehg.mo')
        yield pofile, modir, mofile

def _msgfmt(pofile, mofile):
    modata = Msgfmt(pofile).get()
    with open(mofile, "wb") as fp:
        fp.write(modata)

class build_mo(Command):
    description = "build translations (.mo files)"
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        for pofile, modir, mofile in _walklocales():
            self.mkpath(modir)
            self.make_file(pofile, mofile, _msgfmt, (pofile, mofile))

class import_po(Command):
    description = "import translations (.po file)"
    user_options = [
        ("package=", "p", ("launchpad export package or bzr repo "
                           "[default: launchpad-export.tar.gz]")),
        ("lang=", "l", "languages to be imported, separated by ','"),
        ]

    def initialize_options(self):
        self.package = None
        self.lang = None

    def finalize_options(self):
        if not self.package:
            self.package = 'launchpad-export.tar.gz'

        if self.lang:
            self.lang = self.lang.upper().split(',')

    def _untar(self, name, path='.'):
        with tarfile.open(name, 'r') as tar:
            path = os.path.abspath(path)
            for tarinfo in tar.getmembers():
                # Extract the safe file only
                p = os.path.abspath(os.path.join(path, tarinfo.name))
                if p.startswith(path):
                    tar.extract(tarinfo, path)

    def run(self):
        if not find_executable('msgcat'):
            self.warn("could not find msgcat executable")
            return

        dest_prefix = 'i18n/tortoisehg'
        src_prefix = 'po/tortoisehg'

        log.info('import from %s' % self.package)

        if os.path.isdir(self.package):
            self.bzrrepo = True
            self.package_path = self.package
        elif tarfile.is_tarfile(self.package):
            self.bzrrepo = False
            self.package_path = tempfile.mkdtemp()
            self._untar(self.package, self.package_path)
        else:
            self.warn('%s is not a valid tranlation package' % self.package)
            return

        if self.bzrrepo:
            filter = r'^([\S]+)\.po$'
        else:
            filter = r'^tortoisehg-([\S]+)\.po$'
        r = re.compile(filter)

        src_dir = os.path.join(self.package_path, src_prefix)
        for src_file in os.listdir(src_dir):
            m = r.match(src_file)
            if not m:
                continue

            # filter the language
            lang = m.group(1)
            if self.lang and lang.upper() not in self.lang:
                continue

            dest_file = os.path.join(dest_prefix, lang) + '.po'
            msg = 'updating %s...' % dest_file
            cmd = ['msgcat',
                   '--no-location',
                   '-o', dest_file,
                   os.path.join(src_dir, src_file)
                   ]
            self.execute(spawn, (cmd,), msg)

        if not self.bzrrepo:
            shutil.rmtree(self.package_path)

class update_pot(Command):
    description = "extract translatable strings to tortoisehg.pot"
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        if not find_executable('xgettext'):
            self.warn("could not find xgettext executable, tortoisehg.pot "
                      "won't be built")
            return

        dirlist = [
            '.',
            'contrib',
            'tortoisehg',
            'tortoisehg/hgqt',
            'tortoisehg/util',
            ]

        filelist = []
        for pathname in dirlist:
            if not os.path.exists(pathname):
                continue
            for filename in os.listdir(pathname):
                if filename.endswith('.py'):
                    filelist.append(os.path.join(pathname, filename))
        filelist.sort()

        potfile = 'tortoisehg.pot'

        cmd = [
            'xgettext',
            '--package-name', 'TortoiseHg',
            '--msgid-bugs-address', '<thg-devel@googlegroups.com>',
            '--copyright-holder', thgcopyright,
            '--from-code', 'ISO-8859-1',
            '--keyword=_:1,2c,2t',
            '--add-comments=i18n:',
            '-d', '.',
            '-o', potfile,
            ]
        cmd += filelist
        self.make_file(filelist, potfile, spawn, (cmd,))

class build_config(Command):
    description = 'create config module for unix installation'
    user_options = [
        ('build-lib=', 'd', 'directory to "build" (copy) to'),
        ]

    def initialize_options(self):
        self.build_lib = None

    def finalize_options(self):
        self.set_undefined_options('build',
                                   ('build_lib', 'build_lib'))

    def _generate_config(self, cfgfile):
        from tortoisehg.hgqt import qtcore
        # dirty hack to get the install root
        installcmd = self.get_finalized_command('install')
        rootlen = len(installcmd.root or '')
        sharedir = os.path.join(installcmd.install_data[rootlen:], 'share')
        data = {
            'bin_path': installcmd.install_scripts[rootlen:],
            'license_path': os.path.join(sharedir, 'doc', 'tortoisehg',
                                         'COPYING.txt'),
            'locale_path': os.path.join(sharedir, 'locale'),
            'icon_path': os.path.join(sharedir, 'pixmaps', 'tortoisehg'),
            'nofork': True,
            'qt_api': qtcore._detectapi(),
            }
        # Distributions will need to supply their own
        with open(cfgfile, "w") as f:
            for k, v in sorted(data.items()):
                f.write('%s = %r\n' % (k, v))

    def run(self):
        cfgdir = os.path.join(self.build_lib, 'tortoisehg', 'util')
        cfgfile = os.path.join(cfgdir, 'config.py')
        self.mkpath(cfgdir)
        self.make_file(__file__, cfgfile, self._generate_config, (cfgfile,))

class build_py2app_config(build_config):
    description = 'create config module for standalone OS X bundle'

    def _generate_config(self, cfgfile):
        from tortoisehg.hgqt import qtcore
        # Since py2app seems to ignore the build dir in favor of the src tree,
        # ignore the given path and generate it in the source tree.  The file
        # is conditionalized such that it won't interfere when run from source.
        cwd = os.path.dirname(__file__)
        cfgfile = os.path.join(cwd, 'tortoisehg', 'util', 'config.py')
        data = {
            'license_path': 'COPYING.txt',
            'locale_path': 'locale',
            'icon_path': 'icons',
            'qt_api': qtcore._detectapi(),
        }
        rsrc_dir = 'os.environ["RESOURCEPATH"]'

        with open(cfgfile, "w") as f:
            f.write("import os, sys\n"
                    "\n"
                    "if 'THG_OSX_APP' in os.environ:\n"
                    "    nofork = True\n")
            for k, v in sorted(data.items()):
                f.write("    %s = os.path.join(%s, '%s')\n" % (k, rsrc_dir, v))
            f.write("    bin_path = os.path.dirname(sys.executable)\n")

class build_ui(Command):
    description = 'build PyQt user interfaces (.ui)'
    user_options = [
        ('force', 'f', 'forcibly compile everything (ignore file timestamps)'),
        ]
    boolean_options = ('force',)

    def initialize_options(self):
        self.force = None

    def finalize_options(self):
        self.set_undefined_options('build', ('force', 'force'))

    def _compile_ui(self, ui_file, py_file):
        uic = self._impuic()
        with open(py_file, 'w') as fp:
            uic.compileUi(ui_file, fp)

    @staticmethod
    def _impuic():
        from tortoisehg.hgqt.qtcore import QT_API
        mod = __import__(QT_API, globals(), locals(), ['uic'])
        return mod.uic

    _wrappeduic = False
    @classmethod
    def _wrapuic(cls):
        """wrap uic to use gettext's _() in place of
        QtGui.QApplication.translate as _translate()"""
        if cls._wrappeduic:
            return

        uic = cls._impuic()
        compiler = uic.Compiler.compiler
        qtproxies = uic.Compiler.qtproxies
        indenter = uic.Compiler.indenter

        class _UICompiler(compiler.UICompiler):
            def createToplevelWidget(self, classname, widgetname):
                o = indenter.getIndenter()
                o.level = 0
                o.write('from tortoisehg.util.i18n import _')
                return super().createToplevelWidget(classname,
                                                                     widgetname)
        compiler.UICompiler = _UICompiler

        class _i18n_string(qtproxies.i18n_string):
            def __str__(self):
                # Note: ignoring self.disambig and qtproxies.i18n_context
                return '_(%s)' % qtproxies.as_string(self.string)
        qtproxies.i18n_string = _i18n_string

        cls._wrappeduic = True

    def run(self):
        self._wrapuic()
        basepath = os.path.join(os.path.dirname(__file__), 'tortoisehg', 'hgqt')
        for f in os.listdir(basepath):
            if not f.endswith('.ui'):
                continue
            uifile = os.path.join(basepath, f)
            pyfile = uifile[:-3] + '_ui.py'
            # setup.py is the source of "from i18n import _" line
            self.make_file([uifile, __file__], pyfile,
                           self._compile_ui, (uifile, pyfile))

class build_qrc(Command):
    description = 'build PyQt resource files (.qrc)'
    user_options = [
        ('build-lib=', 'd', 'directory to "build" (copy) to'),
        ('force', 'f', 'forcibly compile everything (ignore file timestamps)'),
        ]
    boolean_options = ('force',)

    def initialize_options(self):
        self.build_lib = None
        self.force = None

    def finalize_options(self):
        self.set_undefined_options('build',
                                   ('build_lib', 'build_lib'),
                                   ('force', 'force'))

    def _findrcc(self):
        from tortoisehg.hgqt.qtcore import QT_API
        try:
            # TODO: Figure out how to handle the removal of pyrcc
            rcc = {'PyQt5': 'pyrcc5'}[QT_API]
        except KeyError:
            raise RuntimeError('unsupported Qt API: %s' % QT_API)
        if os.name != 'nt' or QT_API == 'PyQt5':
            return rcc
        mod = __import__(QT_API, globals(), locals())
        return os.path.join(os.path.dirname(mod.__file__), rcc)

    def _generate_qrc(self, qrc_file, srcfiles, prefix):
        from tortoisehg.hgqt import qtlib
        basedir = os.path.dirname(qrc_file)
        with open(qrc_file, 'w') as f:
            f.write('<!DOCTYPE RCC><RCC version="1.0">\n')
            f.write('  <qresource prefix="%s">\n' % qtlib.htmlescape(prefix))
            for e in srcfiles:
                relpath = e[len(basedir) + 1:]
                f.write('    <file>%s</file>\n'
                        % qtlib.htmlescape(relpath.replace(os.path.sep, '/'),
                                           False))
            f.write('  </qresource>\n')
            f.write('</RCC>\n')

    def _build_rc(self, srcfiles, py_file, basedir, prefix):
        """Generate compiled resource including any files under basedir"""
        # For details, see http://doc.qt.nokia.com/latest/resources.html
        qrc_file = os.path.join(basedir, '%s.qrc' % os.path.basename(basedir))
        try:
            self._generate_qrc(qrc_file, srcfiles, prefix)
            env = os.environ.copy()
            subprocess.check_call(
                [self._findrcc(), qrc_file, '-o', py_file],
                env=env,
                shell=os.name == 'nt'
            )
        finally:
            os.unlink(qrc_file)

    def _build_icons(self, basepath):
        icondir = os.path.join(os.path.dirname(__file__), 'icons')
        iconfiles = []
        for root, dirs, files in os.walk(icondir):
            if root == icondir:
                dirs.remove('svg')  # drop source of .ico files
            iconfiles.extend(os.path.join(root, f) for f in files
                             if f.endswith(('.png', '.svg')))
        pyfile = os.path.join(basepath, 'icons_rc.py')
        # we cannot detect deleted icons
        self.make_file(iconfiles, pyfile,
                       self._build_rc, (iconfiles, pyfile, icondir, '/icons'),
                       exec_msg='generating %s from %s' % (pyfile, icondir))

    def _build_translations(self, basepath):
        """Build translations_rc.py which inclues qt_xx.qm"""
        from tortoisehg.hgqt.qtcore import QT_API
        if QT_API == 'PyQt6':
            from PyQt6.QtCore import QLibraryInfo
            trpath = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
        else:
            from PyQt5.QtCore import QLibraryInfo
            # QLibraryInfo.LibraryPath.PluginsPath is not available with
            # PyQt 5.13.2/Qt 5.9.9
            trpath = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
        builddir = os.path.join(self.get_finalized_command('build').build_base,
                                'qt-translations')
        self.mkpath(builddir)

        # we have to copy .qm files to build directory because .qrc file must
        # specify files by relative paths
        qmfiles = []
        for e in os.listdir(trpath):
            if (not e.startswith(('qt_', 'qscintilla_'))
                or e.startswith('qt_help_')
                or not e.endswith('.qm')):
                continue
            f = os.path.join(builddir, e)
            self.copy_file(os.path.join(trpath, e), f)
            qmfiles.append(f)
        pyfile = os.path.join(basepath, 'translations_rc.py')
        self.make_file(qmfiles, pyfile, self._build_rc,
                       (qmfiles, pyfile, builddir, '/translations'),
                       exec_msg='generating %s from Qt translation' % pyfile)

    def run(self):
        basepath = os.path.join(self.build_lib, 'tortoisehg', 'hgqt')
        self.mkpath(basepath)
        self._build_icons(basepath)
        self._build_translations(basepath)

class clean_local(Command):
    pats = ['*.py[co]', '*_ui.py', '*.mo', '*.orig', '*.rej']
    excludedirs = ['.hg', 'build', 'dist']
    description = 'clean up generated files (%s)' % ', '.join(pats)
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        for e in self._walkpaths('.'):
            log.info("removing '%s'" % e)
            os.remove(e)

    def _walkpaths(self, path):
        for root, _dirs, files in os.walk(path):
            if any(root == os.path.join(path, e)
                   or root.startswith(os.path.join(path, e, ''))
                   for e in self.excludedirs):
                continue
            for e in files:
                fpath = os.path.join(root, e)
                if any(fnmatch(fpath, p) for p in self.pats):
                    yield fpath

class build(_build_orig):
    sub_commands = [
        ('build_config',
         lambda self: (os.name != 'nt' and
                       'py2app' not in self.distribution.commands)),
        ('build_py2app_config',
         lambda self: 'py2app' in self.distribution.commands),
        ('build_ui', None),
        ('build_qrc', lambda self: 'py2exe' in self.distribution.commands),
        ('build_mo', None),
        ] + _build_orig.sub_commands

class clean(_clean_orig):
    sub_commands = [
        ('clean_local', None),
        ] + _clean_orig.sub_commands

    def run(self):
        _clean_orig.run(self)
        for e in self.get_sub_commands():
            self.run_command(e)

cmdclass = {
    'build': build,
    'build_config': build_config,
    'build_py2app_config': build_py2app_config,
    'build_ui': build_ui,
    'build_qrc': build_qrc,
    'build_mo': build_mo,
    'clean': clean,
    'clean_local': clean_local,
    'update_pot': update_pot,
    'import_po': import_po,
    }

def setup_windows(version, setupversion, productname):
    # Specific definitios for Windows NT-alike installations
    _scripts = []
    _data_files = []
    _packages = ['tortoisehg.hgqt', 'tortoisehg.util', 'tortoisehg', 'hgext3rd']
    extra = {}
    hgextmods = []

    # py2exe needs to be installed to work
    try:
        import py2exe

        # The py3 version of py2exe hardcodes _curses in the exclusion list,
        # assuming it is a non-Windows module.  But that prevents
        # windows-curses from being bundled.
        #
        # See https://github.com/py2exe/py2exe/issues/132
        from py2exe import hooks
        hooks.windows_excludes.remove('_curses')

        # py2exe hardcodes the site module in the exclusion list, but also
        # sets interpreter flags to disable the default initialization, so
        # the global site and user site packages aren't changed by including
        # it.  But that means excluding utility functions for building the
        # user site path and updating builtins to have quit() and exit()
        # that are available with vanilla python, PyOxidizer, and py2app.
        #
        # See https://github.com/py2exe/py2exe/issues/154
        hooks.windows_excludes.remove('site')

        # Help py2exe to find win32com.shell
        try:
            import modulefinder
            import win32com
            for p in win32com.__path__[1:]: # Take the path to win32comext
                modulefinder.AddPackagePath("win32com", p)
            pn = "win32com.shell"
            __import__(pn)
            m = sys.modules[pn]
            for p in m.__path__[1:]:
                modulefinder.AddPackagePath(pn, p)
        except ImportError:
            pass

    except ImportError:
        if '--version' not in sys.argv:
            raise

    # Allow use of environment variables to specify the location of Mercurial
    import modulefinder
    path = os.getenv('MERCURIAL_PATH')
    if path:
        modulefinder.AddPackagePath('mercurial', path)
    path = os.getenv('HGEXT_PATH')
    if path:
        modulefinder.AddPackagePath('hgext', path)
    modulefinder.AddPackagePath('hgext3rd', 'hgext3rd')

    if 'py2exe' in sys.argv:
        import hgext
        hgextdir = os.path.dirname(hgext.__file__)
        hgextmods = {"hgext." + os.path.splitext(f)[0]
                         for f in os.listdir(hgextdir)}
        # most icons are packed into Qt resource, but .ico files must reside
        # in filesystem so that shell extension can read them
        root = 'icons'
        _data_files.append((root,
                            [os.path.join(root, f) for f in os.listdir(root)
                             if f.endswith('.ico') or f == 'README.txt']))

    from tortoisehg.hgqt.qtcore import QT_API

    # for PyQt, see http://www.py2exe.org/index.cgi/Py2exeAndPyQt
    if QT_API == 'PyQt6':
        includes = ['PyQt6.sip']
    else:
        includes = ['PyQt5.sip']

    # Qt4 plugins, see http://stackoverflow.com/questions/2206406/
    def qt6_plugins(subdir, *dlls):
        from PyQt6.QtCore import QLibraryInfo
        pluginsdir = QLibraryInfo.path(QLibraryInfo.LibraryPath.PluginsPath)
        return subdir, [os.path.join(pluginsdir, subdir, e) for e in dlls]

    def qt5_plugins(subdir, *dlls):
        from PyQt5.QtCore import QLibraryInfo
        # QLibraryInfo.LibraryPath.PluginsPath is not available with
        # PyQt 5.13.2/Qt 5.9.9
        pluginsdir = QLibraryInfo.location(QLibraryInfo.PluginsPath)
        # py2 somehow mangles the path generated here in the form of
        # C:/Qt/Qt5.9.9/5.9.9/msvc2017_64/plugins\\platforms\\qwindows.dll
        # to C:Qt/Qt5.9.9/... in the caller unless this is normalized.
        pluginsdir = os.path.normpath(pluginsdir)
        return subdir, [os.path.join(pluginsdir, subdir, e) for e in dlls]

    from tortoisehg.hgqt.qtcore import QT_API
    if QT_API == 'PyQt6':
        _data_files.append(qt6_plugins('platforms', 'qwindows.dll'))
        _data_files.append(qt6_plugins('styles', 'qwindowsvistastyle.dll'))
        _data_files.append(qt6_plugins('imageformats',
                                       'qico.dll', 'qsvg.dll', 'qjpeg.dll',
                                       'qgif.dll', 'qicns.dll', 'qtga.dll',
                                       'qtiff.dll', 'qwbmp.dll', 'qwebp.dll'))
    else:
        _data_files.append(qt5_plugins('platforms', 'qwindows.dll'))
        _data_files.append(qt5_plugins('styles', 'qwindowsvistastyle.dll'))
        _data_files.append(qt5_plugins('imageformats',
                                       'qico.dll', 'qsvg.dll', 'qjpeg.dll',
                                       'qgif.dll', 'qicns.dll', 'qtga.dll',
                                       'qtiff.dll', 'qwbmp.dll', 'qwebp.dll'))

    # Manually include other modules py2exe can't find by itself.
    if 'hgext.highlight' in hgextmods:
        includes += ['pygments.*', 'pygments.lexers.*', 'pygments.formatters.*',
                     'pygments.filters.*', 'pygments.styles.*']
    if 'hgext.patchbomb' in hgextmods:
        includes += ['email.*', 'email.mime.*']

    extra['options'] = {}
    extra['options']['py2exe'] = {
        "skip_archive": 0,
        # Don't pull in all this MFC stuff used by the makepy UI.
        "excludes": ("pywin,pywin.dialogs,pywin.dialogs.list,setuptools"
                     "setup,distutils"),  # required only for in-place use
        "includes": includes,
        "optimize": 1,
        }
    extra['console'] = [
        {'script': 'thg',
         'icon_resources': [(0, 'icons/thg_logo.ico')],
         'description': 'TortoiseHg GUI tools for Mercurial SCM',
         'copyright': thgcopyright,
         'internal_name': 'thg',
         'product_name': productname,
         'product_version': version,
         'version': setupversion,
         },
        {'script': 'contrib/hg',
         'icon_resources': [(0, 'icons/hg.ico')],
         'description': 'Mercurial Distributed SCM',
         'copyright': hgcopyright,
         'internal_name': 'hg',
         'product_name': productname,
         'product_version': version,
         'version': setupversion,
         },
        {'script': 'win32/docdiff.py',
         'icon_resources': [(0, 'icons/TortoiseMerge.ico')],
         'description': 'TortoiseHg binary document diff tool',
         'copyright': thgcopyright,
         'internal_name': 'docdiff',
         'product_name': productname,
         'product_version': version,
         'version': setupversion,
         },
        ]
    extra['windows'] = [
        {'script': 'thg',
         'dest_base': 'thgw',
         'icon_resources': [(0, 'icons/thg_logo.ico')],
         'description': 'TortoiseHg GUI tools for Mercurial SCM',
         'copyright': thgcopyright,
         'internal_name': 'thgw',
         'product_name': productname,
         'product_version': version,
         'version': setupversion,
         },
        {'script': 'TortoiseHgOverlayServer.py',
         'icon_resources': [(0, 'icons/thg_logo.ico')],
         'description': 'TortoiseHg Overlay Icon Server',
         'copyright': thgcopyright,
         'internal_name': 'TortoiseHgOverlayServer',
         'product_name': productname,
         'product_version': version,
         'version': setupversion,
         },
        ]
    # put dlls in sub directory so that they won't pollute PATH
    extra['zipfile'] = 'lib/library.zip'

    return _scripts, _packages, _data_files, extra

def setup_osx(version):
    _extra = {}

    # This causes py2app to copy the scripts into build/ and then adjust the
    # mode, but the build dir is ignored for some reason.
    _scripts = ['thg']

    _packages = ['tortoisehg.hgqt', 'tortoisehg.util', 'tortoisehg', 'hgext3rd']
    _data_files = []

    def qt5_plugins(subdir, *dlls):
        from PyQt5.QtCore import QLibraryInfo
        # QLibraryInfo.LibraryPath.PluginsPath is not available with
        # PyQt 5.13.2/Qt 5.9.9
        pluginsdir = QLibraryInfo.location(QLibraryInfo.PluginsPath)
        return subdir, [os.path.join(pluginsdir, subdir, e) for e in dlls]
    def qt6_plugins(subdir, *dlls):
        from PyQt6.QtCore import QLibraryInfo
        pluginsdir = QLibraryInfo.path(QLibraryInfo.LibraryPath.PluginsPath)
        return subdir, [os.path.join(pluginsdir, subdir, e) for e in dlls]


    from tortoisehg.hgqt.qtcore import QT_API
    if QT_API == 'PyQt6':
        _data_files.append(qt6_plugins('platforms', 'libqcocoa.dylib'))
        _data_files.append(qt6_plugins('imageformats', 'libqsvg.dylib'))
    else:
        _data_files.append(qt5_plugins('platforms', 'libqcocoa.dylib'))
        _data_files.append(qt5_plugins('imageformats', 'libqsvg.dylib'))

    _py2app_options = {
        'arch': 'x86_64',
        'argv_emulation': False,
        'no_chdir': True,
        'excludes': ['Carbon', 'curses', 'distools', 'distutils', 'docutils',
                     'PyQt4.phonon', 'PyQt4.QtDeclarative', 'PyQt4.QtDesigner',
                     'PyQt4.QtHelp', 'PyQt4.QtMultimedia', 'PyQt4.QtOpenGL',
                     'PyQt4.QtScript', 'PyQt4.QtScriptTools', 'PyQt4.QtSql',
                     'PyQt4.QtTest', 'PyQt4.QtWebKit', 'PyQt4.QtXmlPatterns',
                     'PyQt4.QtXmlPatterns', 'PyQt5.QtDBus',
                     'PyQt5.QtDeclarative', 'PyQt5.QtDesigner', 'PyQt5.QtHelp',
                     'PyQt5.QtMultimedia', 'PyQt5.QtOpenGL', 'PyQt5.QtScript',
                     'PyQt5.QtScriptTools', 'PyQt5.QtSql', 'PyQt5.QtTest',
                     'PyQt5.QtWebKit', 'PyQt5.QtXmlPatterns', 'PyQt5.phonon',
                     'py2app', 'setup', 'setuptools', 'unittest', 'PIL'],

        'extra_scripts': ['contrib/hg'],
        'iconfile': 'contrib/packaging/macos/TortoiseHg.icns',
        'includes': ['email.mime.text', 'sip'],
        'packages': ['certifi', 'hgext', 'iniparse', 'keyring', 'mercurial',
                     'pygments', 'tortoisehg'],

        'plist': {
            'CFBundleDisplayName': 'TortoiseHg',
            'CFBundleExecutable': 'TortoiseHg',
            'CFBundleIdentifier': 'org.tortoisehg.thg',
            'CFBundleName': 'TortoiseHg',
            'CFBundleShortVersionString': version,
            'CFBundleVersion': version,
            'LSEnvironment': {
                # because launched app can't inherit environment variables from
                # console, the encoding would be set to "ascii" by default
                'HGENCODING': 'utf-8',
                'THG_OSX_APP': '1',
                'QT_API': QT_API,
            },
            'NSHumanReadableCopyright': thgcopyright,
        },

        'resources': ['COPYING.txt', 'icons', 'locale'],
    }

    _extra['app'] = ['thg']
    _extra['setup_requires'] = ['py2app']
    _extra['options'] = {'py2app': _py2app_options}

    return _scripts, _packages, _data_files, _extra

def setup_posix():
    # Specific definitios for Posix installations
    _extra = {}
    _scripts = ['thg']
    _packages = ['tortoisehg', 'tortoisehg.hgqt', 'tortoisehg.util', 'hgext3rd']
    _data_files = []
    # .svg and .png are loaded by thg, .ico by nautilus extension
    for root, dirs, files in os.walk('icons'):
        if root == 'icons':
            dirs.remove('svg')  # drop source of .ico files
        # do not add directories to the packing list
        if not files:
            continue
        _data_files.append((os.path.join('share/pixmaps/tortoisehg', root[6:]),
                            [os.path.join(root, f) for f in files]))
    # install SVG icon for the desktop file
    _data_files.append(('share/pixmaps', ['icons/svg/thg_logo.svg']))
    _data_files.append(('share/doc/tortoisehg', ['COPYING.txt']))
    _data_files.extend((os.path.join('share', modir), [mofile])
                       for pofile, modir, mofile in _walklocales())
    _data_files += [('share/nautilus-python/extensions',
                     ['contrib/nautilus-thg.py'])]

    return _scripts, _packages, _data_files, _extra

if __name__ == '__main__':
    version = ''

    if os.path.isdir('.hg'):
        from tortoisehg.util import version as _version
        branch, version = _version.liveversion()
        if version.endswith('+'):
            version += time.strftime('%Y%m%d')
    elif os.path.exists('.hg_archival.txt'):
        with open('.hg_archival.txt') as fp:
            kw = dict([t.strip() for t in l.split(':', 1)] for l in fp)
        if 'tag' in kw:
            version = kw['tag']
        elif 'latesttag' in kw:
            version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
        else:
            version = kw.get('node', '')[:12]

    if version:
        with open("tortoisehg/util/__version__.py", "w") as f:
            f.write('# this file is autogenerated by setup.py\n')
            f.write('version = "%s"\n' % version)

    try:
        import tortoisehg.util.__version__
        version = tortoisehg.util.__version__.version
    except ImportError:
        version = 'unknown'

    if os.name == "nt":
        # Windows binary file versions for exe/dll files must have the
        # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
        from tortoisehg.util.version import package_version
        setupversion = package_version()
        productname = 'TortoiseHg'
        (scripts, packages, data_files, extra) = setup_windows(
            version, setupversion, productname,
        )
    elif sys.platform == "darwin" and 'py2app' in sys.argv[1:]:
        (scripts, packages, data_files, extra) = setup_osx(version)
        setupversion = version
        productname = 'TortoiseHg'
    else:
        (scripts, packages, data_files, extra) = setup_posix()
        setupversion = version
        productname = 'tortoisehg'

    setup(name=productname,
          version=setupversion,
          author='Steve Borho',
          author_email='steve@borho.org',
          url='https://tortoisehg.bitbucket.io',
          description='TortoiseHg dialogs for Mercurial VCS',
          license='GNU GPLv2 or any later version',
          scripts=scripts,
          packages=packages,
          data_files=data_files,
          cmdclass=cmdclass,
          **extra)
