# 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.

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-2022 Steve Borho and others'
hgcopyright = 'Copyright (C) 2005-2022 Matt Mackall and others'

if sys.version_info[0] >= 3:
    unicode = str  # pycompat.unicode

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(_UICompiler, self).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()
            if os.name == 'nt' and 'VIRTUAL_ENV' in env and sys.version_info[0] == 2:
                # pyrcc5.bat gets installed to the root of the virtualenv, not
                # in the Scripts directory with the binaries on PATH.
                env['PATH'] = '%s%s%s' % (
                    env.get('PATH'), os.pathsep, env.get('VIRTUAL_ENV')
                )
            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 = unicode(
                QLibraryInfo.location(QLibraryInfo.LibraryPath.TranslationsPath))
        else:
            if os.name == 'nt' and sys.version_info[0] == 2:
                import PyQt5
                trpath = os.path.join(
                    os.path.dirname(PyQt5.__file__), 'translations')
            else:
                from PyQt5.QtCore import QLibraryInfo
                # QLibraryInfo.LibraryPath.PluginsPath is not available with
                # PyQt 5.13.2/Qt 5.9.9
                trpath = unicode(
                    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

        if sys.version_info.major >= 3:
            # 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')

        # 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 = set(["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.location(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.location(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)
