#!/usr/bin/python3
# -*- coding: utf-8 -*-

#  Copyright © 2009, 2011-2017  B. Clausius <barcc@gmx.de>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

# pylint: disable=W0201


from glob import glob
import os
import sys
import sysconfig
from contextlib import suppress
from collections import OrderedDict

use_setuptools = not os.getenv('PYBIK_NO_SETUPTOOLS')
if use_setuptools:
    try:
        import setuptools
    except ImportError:
        use_setuptools = False
if not use_setuptools:
    from distutils.core import setup
    from distutils.cmd import Command
    from distutils.extension import Extension
    from distutils import dist
    from distutils.command.install import install as install_orig
else:
    print('using setuptools')
    from setuptools import setup
    from setuptools import Command
    from setuptools import Extension
    from setuptools import dist
    from setuptools.command.install import install as install_orig

from distutils.dir_util import remove_tree
from distutils.file_util import move_file
from distutils.dep_util import newer, newer_group
from distutils.log import warn, info, error, debug

import distutils.command.build
import distutils.command.build_ext
import distutils.command.build_scripts
import distutils.command.install_data
import distutils.command.install_lib
import distutils.command.clean
import distutils.command.sdist

#from Cython.Distutils import build_ext

#print('using', 'setuptools' if use_setuptools else 'distutils')

# the clean command should leave a clean tree
sys.dont_write_bytecode = True
os.environ['PYTHONDONTWRITEBYTECODE'] = '1'

import buildlib.utils
import pybiklib
from pybiklib import config

os.environ['QT_SELECT'] = '5'


class Distribution (distutils.dist.Distribution):
    def __init__(self, attrs=None):
        self.bug_contact = None
        self.appmeta_templates = None
        #XXX: Mangle alpha/beta/rc versions to PEP440 version scheme for setuptools, pip, pypi ...
        version = attrs['version']
        pep440 = version.replace('~alpha', 'a').replace('~beta', 'b').replace('~rc', 'rc')
        attrs['version'] = pep440
        distutils.dist.Distribution.__init__(self, attrs=attrs)
        if version != pep440:
            self.metadata.version = version


class build(distutils.command.build.build):
    """Adds extra commands to the build target."""
    user_options = distutils.command.build.build.user_options + [
            ('inplace', 'i', 'ignore build-lib and put compiled modules into the source directory'),
            #XXX: In Python 3.5 distutils.command.build.build has a parallel option
            ('parallel=', None, 'number of parallel build jobs or "True"'),
            ('arch-only', None, 'Build only architecture dependent files'),
            ('indep-only', None, 'Build only architecture independent files'),
            ('pickle-protocol=', None, 'Pickle protocol for modeldata (default: highest protocol)'),
            ('maxsize=', None, 'Maximum model size (default: 10)'),
            ('reproducible', None, 'Build modeldata reproducible (experimental)'),
            ('variants=',    None, 'Build extension variants'),
            ('armhf',        None, 'Cross build for armhf'),
            ('debugflags=',  None, 'Comma-separated list of debug flags'),
        ]
    boolean_options = distutils.command.build.build.boolean_options + [
            'inplace', 'arch-only', 'indep-only', 'reproducible',
            'armhf',
        ]
        
    def has_pure_modules(self):
        return self.indep_only and distutils.command.build.build.has_pure_modules(self)
        
    _commands_dict = dict(distutils.command.build.build.sub_commands)
    # pylint: disable=W0212
    sub_commands = [('build_py',      has_pure_modules),
                    ('build_ext',     lambda self: self.arch_only and self._commands_dict['build_ext'](self)),
                    ('build_scripts', lambda self: self.indep_only and self._commands_dict['build_scripts'](self)),
                    ('build_models',  lambda self: self.indep_only),
                    ('build_appicons',lambda self: self.indep_only and not self.inplace),
                    ('build_i18n',    lambda self: self.indep_only),
                    ('build_appmeta', lambda self: self.indep_only),
                    ('build_man',     lambda self: self.indep_only and not self.inplace),
                ]
    # pylint: enable=W0212
                
    def initialize_options(self):
        distutils.command.build.build.initialize_options(self)
        self.inplace = 0
        self.parallel = None
        self.arch_only = False
        self.indep_only = False
        self.pickle_protocol = '-1'
        self.maxsize = None
        self.reproducible = False
        self.variants = None
        self.armhf = False
        self.debugflags = None
        
    def finalize_options(self):
        # In Python 3.5 distutils.command.build.build has a parallel option
        # convert 'true' to True before the base class fails
        if isinstance(self.parallel, str) and self.parallel.lower() == 'true':
            self.parallel = True
        distutils.command.build.build.finalize_options(self)
        if not self.arch_only and not self.indep_only:
            self.arch_only = self.indep_only = True
        
        
class build_csrc (distutils.command.build_ext.build_ext):
    description = 'build C source code with py2pyx and cython'
    accepted_variants = buildlib.utils.accepted_variants()
    user_options = distutils.command.build_ext.build_ext.user_options + [
        ('cython-opts=', None,          'Cython options'),
        ('variants=',    None,          'Build extension variants (comma-separated list of: %s)' %
                                        buildlib.utils.variant_string()),
        ('armhf',        None,          'Cross build for armhf'),
        ('debugflags=',  None,          'Comma-separated list of debug flags'),
    ]
    
    def initialize_options(self):
        distutils.command.build_ext.build_ext.initialize_options(self)
        self.inplace = None
        self.cython_opts = ''
        self.variants = None
        self.armhf = None
        self.debugflags = None
        self.cc_files = []
        
    def finalize_options(self):
        # deactivate parallel build in Python 3.5, does not work with this command
        self.parallel = 1
        distutils.command.build_ext.build_ext.finalize_options(self)
        self.set_undefined_options('build',
                                       ('inplace', 'inplace'),
                                       ('variants', 'variants'),
                                       ('armhf', 'armhf'),
                                       ('debugflags', 'debugflags'),
                                    )
        if self.variants is None:
            self.variants = []
        else:
            self.variants = self.variants.split(',')
            for v in self.variants:
                if v not in self.accepted_variants:
                    raise ValueError('unknown variant: ' + v)
                    
        if self.armhf:
            from distutils.util import get_platform
            self.build_lib = self.build_lib.replace(get_platform(), 'linux-armv7l')
            self.build_temp = self.build_temp.replace(get_platform(), 'linux-armv7l')
        self.cython_opts = self.cython_opts.split()
        import pybiklib.debug
        if self.debugflags is not None:
            pybiklib.debug.set_flagstring(self.debugflags)
        
        # aliases
        groups = []
        aliases = OrderedDict({'': ''})
        for v in buildlib.utils.variants:
            if v['type'] is None or v['group'] in groups:
                continue
            groups.append(v['group'])
            if v['type'] == 'enum':
                for a, va in list(aliases.items()):
                    aliases[a] = va + (v['id'] if v['group'] != 'glvariant' else '???')
                    for ve in buildlib.utils.variants:
                        if ve['type'] == 'enum' and ve['group'] == v['group']:
                            aliases[a+ve['id']] = va+ve['id']
            elif v['type'] == 'bool':
                for a, va in list(aliases.items()):
                    aliases[a+v['id']] = va+v['id']
        self.aliases = OrderedDict((k,v) for k,v in aliases.items() if k != v)
        
    def make_files(self, infiles, outfiles, func, args, exec_msg=None, skip_msg=None, level=1):
        """Works like make_file, but for operations
        that generate one or more output files.
        """
        if skip_msg is None:
            skip_msg = "skipping %s (inputs unchanged)" % outfiles
        if isinstance(infiles, str):
            infiles = (infiles,)
        elif not isinstance(infiles, (list, tuple)):
            raise TypeError("'infiles' must be a string, or a list or tuple of strings")
        if exec_msg is None:
            exec_msg = "generating %s from %s" % (outfiles, ', '.join(infiles))
        if self.force or any(newer_group(infiles, outfile) for outfile in outfiles):
            self.execute(func, args, exec_msg, level)
        else:
            debug(skip_msg)
            
    def compare_file(self, filename1, filename2):
        if self.force or not os.path.exists(filename2):
            return False
        with open(filename1, 'rb') as file1, open(filename2, 'rb') as file2:
            return file1.read() == file2.read()
            
    @staticmethod
    def run_py2pyx(py_file, pyx_file, pxd_file, pxm_file, pxc_file):
        info('py2pyx: %s --> %s' % (py_file, pyx_file))
        from buildlib.py2pyx import create_pyx, Py2pyxError
        try:
            create_pyx(py_file, pyx_file, pxd_file, pxm_file, pxc_file)
        except Py2pyxError as e:
            error("error: %s", e)
            sys.exit(1)
        
    identifier_translate = bytes((i if bytes([i]).isalnum() else ord('_')) for i in range(256))
    
    @classmethod
    def replace_cwd(cls, filename):
        with open(filename, 'rb') as f:
            text = f.read()
        cwdb_esc = bytes(ord(c) for c in os.getcwd()[:32] if ord(c)<128).translate(cls.identifier_translate)
        text = text.replace(os.getcwdb(), b'/tmp').replace(cwdb_esc, b'_tmp')
        with open(filename, 'wb') as f:
            f.write(text)
            
    def run_cython(self, cython_opts, infile, outfile):
        from Cython.Compiler.CmdLine import parse_command_line
        from Cython.Compiler.Main import compile
        
        info('cython: %s --> %s' % (infile, outfile))
        args = ['-3', '-o', outfile] + cython_opts + [infile]
        options, unused_sources = parse_command_line(args)
        try:
            result = compile([infile], options)
        except Exception as e:
            error("error: %s", e)
            sys.exit(1)
        if result.num_errors > 0:
            sys.exit(1)
        self.replace_cwd(outfile)
        
    def build_pyx_(self, py_file, pyx_file, pxd_file, pxm_file, pxc_file):
        pxd_file_tmp = pxd_file + '.tmp'
        pxm_file_tmp = pxm_file + '.tmp'
        pxc_file_tmp = pxc_file + '.tmp'
        self.run_py2pyx(py_file, pyx_file, pxd_file_tmp, pxm_file_tmp, pxc_file_tmp)
        for px_file, px_file_tmp in ((pxd_file, pxd_file_tmp), (pxm_file, pxm_file_tmp), (pxc_file, pxc_file_tmp)):
            if os.path.exists(px_file_tmp):
                if self.compare_file(px_file_tmp, px_file):
                    os.remove(px_file_tmp)
                    info("unchanged file '%s'", px_file)
                else:
                    if os.path.exists(px_file):
                        os.remove(px_file)
                    move_file(px_file_tmp, px_file)
            elif os.path.exists(px_file):
                os.remove(px_file)
                
    def build_pyx(self, py_file, pyx_file, pxd_file, pxm_file, pxc_file):
        self.mkpath(os.path.dirname(pyx_file))
        self.make_files([py_file], pyx_file, self.build_pyx_, (py_file, pyx_file, pxd_file, pxm_file, pxc_file))
        
    @staticmethod
    def find_qt_command(command):
        from shutil import which
        return which(command) or which(command+'-qt5') or command
        
    def create_extension_variant(self, extension, variantflags, variant_map):
        variantstr = ''.join(variantflags)
        info('generating extension %r with variant %r', extension.name, variantstr)
        def filename_copy(filename):
            return os.path.join(self.build_temp, filename)
        def make_copy(in_file, out_file):
            self.mkpath(os.path.dirname(out_file))
            self.copy_file(in_file, out_file)
        def filename_variant(base, ext):
            if '_' in base.lstrip('_'):
                src_base, src_variant = base.rsplit('_', 1)
                if src_variant in self.accepted_variants:
                    if src_variant not in variantflags:
                        return None
                    base = src_base
            return os.path.join(self.build_temp, base+'_'+variantstr+ext)
        def make_variant(in_file, out_file):
            self.mkpath(os.path.dirname(out_file))
            self.copy_file(in_file, out_file)
            from buildlib.utils import modify_file
            info('changing to variant %r in %r, %s', variantstr, out_file, variantflags)
            for modulename, mvariantflags in variant_map.items():
                mvariantstr = ''.join(mvariantflags)
                modified = modify_file(out_file, [(r'\[\[%s_VARIANT\]\]'%modulename, modulename+'_'+mvariantstr)], count=0)
                if not modified:
                    warn('unchanged file: %r for module-variant: %s_%s', out_file, modulename, mvariantstr)
            for v in variantflags:
                vgroup = self.accepted_variants[v]['group'].upper()
                vid = self.accepted_variants[v]['id']
                subs = [(r'\[\[%s\]\]' % vgroup, vid)]
                if self.accepted_variants[v]['type'] == 'bool':
                    subs.append((r'\b(DEF %s = )False' % vgroup, r'\1True'))
                modified = modify_file(out_file, subs, count=0)
                if not modified:
                    warn('unchanged file: %r for variant %r', out_file, v)
            modified = modify_file(out_file, [(r'\[\[VARIANT\]\]', variantstr)], count=0)
            if not modified:
                warn('unchanged file: %r for variant %r', out_file, variantstr)
            def depends_lines():
                yield '{'
                for vs, dvs in self.variant_map.items():
                    joinkey = repr(''.join(vs)) + ':'
                    joinvalue = list(k + '_' + ''.join(vs) for k, vs in dvs.items())
                    yield '  {:12} {!r},'.format(joinkey, joinvalue)
                yield '}'
            modified = modify_file(out_file, [(r'\[\[DEPENDS\]\]', '\n  '.join(depends_lines()))])
            def aliases_lines():
                yield '{'
                for avs, vs in self.aliases.items():
                    yield '  {:12} {!r},'.format(repr(avs) + ':', vs)
                yield '}'
            modified = modify_file(out_file, [(r'\[\[ALIASES\]\]', '\n  '.join(aliases_lines()))])
            
        def get_libs():
            for template in extension.libraries:
                vlibs = variant_libraries.get(template)
                if vlibs is None:
                    yield template
                else:
                    for v in variantflags:
                        yield from vlibs.get(v, [])
        extvariant = Extension(extension.name+'_'+variantstr, sources=[],
                               depends=[],
                               language=extension.language,
                               define_macros=extension.define_macros,
                               include_dirs=extension.include_dirs,
                               libraries=list(get_libs()),
                              )
        for in_file in extension.sources:
            base, ext = os.path.splitext(in_file)
            out_file = filename_variant(base, ext)
            if out_file is None:
                continue
            self.make_file([in_file], out_file, make_variant, [in_file, out_file])
            extvariant.sources.append(out_file)
        for in_file in extension.depends:
            base, ext = os.path.splitext(in_file)
            if ext == '.py':
                continue
            if 'qtq' not in variantflags and os.path.basename(in_file) == 'qtq.pxd':
                continue
            if os.path.isfile(base+'.pyx'):
                out_file = filename_variant(base, ext)
                if out_file is None:
                    continue
                self.make_file([in_file], out_file, make_variant, [in_file, out_file])
            else:
                out_file = filename_copy(in_file)
                self.make_file([in_file], out_file, make_copy, [in_file, out_file])
            extvariant.depends.append(out_file)
        return extvariant
        
    def build_extensions(self):
        def gen_extensions(variants, variantflags, selected=True):
            if variants:
                group, vs, all_vs = variants[0]
                for v in all_vs:
                    yield from gen_extensions(variants[1:],
                                              variantflags if v is None else variantflags + ((group, v),),
                                              selected and v in vs)
            else:
                yield selected, variantflags
                
        variants = list(buildlib.utils.group_variants(self.variants))
        self.variant_map = OrderedDict()
        for extension in self.extensions:
            built_variants = []
            modulename = extension.name.split('/')[-1]
            for selected, variantflags in gen_extensions(variants, ()):
                filtered_variantflags = tuple(vf for g, vf in variantflags
                                                 if g in variantgroups_by_module[modulename])
                variantflags = tuple(vf for g, vf in variantflags)
                if 'd' not in variantflags and modulename == '_debug':
                    continue
                if 'qtw' not in variantflags and modulename == '_qtui':
                    continue
                variant_map = self.variant_map.setdefault(variantflags, OrderedDict())
                assert modulename not in variant_map
                if modulename != '_qtexec':
                    variant_map[modulename] = filtered_variantflags
                if not selected:
                    continue
                if filtered_variantflags in built_variants:
                    continue
                extvariant = self.create_extension_variant(extension, filtered_variantflags, variant_map)
                self.build_extension(extvariant)
                built_variants.append(filtered_variantflags)
                        
    def build_extension(self, extension):
        sources = extension.sources
        depends = sources + extension.depends
            
        py_files = []
        pyx_files = []
        pyx_files_dep = []
        ui_files = []
        moc_files = []
        c_files = []
        c_files_dep = []
        
        for in_file in extension.sources:
            base, ext = os.path.splitext(in_file)
            if ext == '.py':
                py_files.append(in_file)
            elif ext == '.pyx':
                pyx_files.append(in_file)
            else:
                assert False, in_file
        assert len(py_files) <= 1
        for in_file in extension.depends:
            base, ext = os.path.splitext(in_file)
            if ext == '.py':
                basename = os.path.basename(base)
                dirname = os.path.dirname(base)
                pxd_file = os.path.join(dirname, '_%s.pxd' % basename)
                pyx_files_dep.append(pxd_file)
            elif ext == '.pxd':
                pyx_files_dep.append(in_file)
            elif ext == '.ui':
                ui_files.append(in_file)
            elif ext == '.h':
                c_files_dep.append(in_file)
            else:
                assert False, in_file
            
        # generate files from py-files with #px annotations
        for in_file in py_files:
            base, ext = os.path.splitext(in_file)
            assert ext == '.py'
            basename = os.path.basename(base)
            dirname = os.path.dirname(base)
            pyx_file = os.path.join(dirname, '_%s.pyx' % basename)
            pxd_file = os.path.join(dirname, '_%s.pxd' % basename)
            pxm_file = os.path.join(dirname, '_%s_moc.h' % basename)
            pxc_file = os.path.join(dirname, '_%s_priv.cpp' % basename)
            self.build_pyx(in_file, pyx_file, pxd_file, pxm_file, pxc_file)
            pyx_files.append(pyx_file)
            if os.path.exists(pxd_file):
                pyx_files_dep.append(pxd_file)
            if os.path.exists(pxm_file):
                c_files_dep.append(pxm_file)
                moc_files.append(pxm_file)
            if os.path.exists(pxc_file):
                c_files.append(pxc_file)
                
        def use_cc(out_file):
            inplace_file = os.path.join('csrc', os.path.basename(out_file))
            if os.path.exists(inplace_file) and not self.force:
                self.mkpath(os.path.dirname(out_file))
                self.copy_file(inplace_file, out_file)
                return True
            return False
            
        # generate C-files with cython
        if extension.language == 'c++':
            c_file_ext = '.cpp'
            cython_opts = ['--cplus'] + self.cython_opts
        else:
            c_file_ext = '.c'
            cython_opts = self.cython_opts
        assert len(pyx_files) <= 1, pyx_files
        for in_file in pyx_files:
            base, ext = os.path.splitext(in_file)
            assert ext == '.pyx'
            out_file = base + c_file_ext
            if not use_cc(out_file):
                self.make_file(pyx_files+pyx_files_dep, out_file,
                               self.run_cython, (cython_opts, in_file, out_file))
            c_files.append(out_file)
            
        # generate C++-files with uic
        uic_cmd = self.find_qt_command('uic')
        for in_file in ui_files:
            base, ext = os.path.splitext(in_file)
            assert ext == '.ui'
            out_file = base + '.h'
            if not use_cc(out_file):
                self.make_file([in_file], out_file, self.spawn, (
                        [uic_cmd, in_file, '-o', out_file, '--translate', 'gettext_translate'],))
            c_files_dep.append(out_file)
            
        # generate C++-files with moc
        moc_cmd = self.find_qt_command('moc')
        for in_file in moc_files:
            base, ext = os.path.splitext(in_file)
            assert in_file.endswith('_moc.h')
            out_file = base + '.cpp'
            if not use_cc(out_file):
                self.make_file([in_file], out_file, self.spawn, ([moc_cmd, in_file, '-o', out_file],))
            c_files.append(out_file)
            
        extension.depends = c_files_dep
        extension.sources = c_files
        self.cc_files += c_files + c_files_dep
        
        
class build_ext (build_csrc):
    description = distutils.command.build_ext.build_ext.description
    
    def get_ext_filename(self, ext_name):
        filename = distutils.command.build_ext.build_ext.get_ext_filename(self, ext_name)
        if self.armhf:
            from distutils.sysconfig import get_config_var
            multiarch = get_config_var('MULTIARCH')
            filename = filename.replace(multiarch, 'arm-linux-gnueabihf')
        return filename
        
    def build_extension(self, extension):
        if sys.flags.debug:
            extension.define_macros.extend([('QT_DECLARATIVE_DEBUG', None), ('QT_QML_DEBUG', None)])
        if self.armhf:
            multiarch = 'arm-linux-gnueabihf'
        else:
            multiarch = sysconfig.get_config_var('MULTIARCH')
            if multiarch is None:
                multiarch = sysconfig.get_config_var('multiarchsubdir') or ''
                multiarch = multiarch.strip('/')
        extension.include_dirs = [d.replace('<multiarchsubdir>', multiarch)
                                  for d in extension.include_dirs]
        sources = extension.sources
        ext_path = self.get_ext_fullpath(extension.name)
        depends = sources + extension.depends
        if self.force or newer_group(depends, ext_path, 'newer'):
            info('building C-Code for %r extension', extension.name)
        else:
            debug('skipping %r extension (up-to-date)', extension.name)
            return
            
        # build C-code
        build_csrc.build_extension(self, extension)
        # build extension module from C-code
        if self.armhf:
            info('cross building for armhf')
            os.environ['CC'] = 'arm-linux-gnueabihf-gcc'
            os.environ['CXX'] = 'arm-linux-gnueabihf-g++'
        with suppress(AttributeError, ValueError):
            import distutils.sysconfig
            distutils.sysconfig.customize_compiler(self.compiler)
            if extension.language == 'c++':
                #XXX: distutils uses the wrong compiler (not a problem with gcc) and flags (-Wstrict-prototypes) for C++ code
                #     silence cc1plus: warning: command line option '-Wstrict-prototypes' is valid for C/ObjC but not for C++
                #     http://bugs.python.org/issue1222585
                #     http://bugs.python.org/issue9031
                if '-Wstrict-prototypes' in self.compiler.compiler_so:
                    info("removing command line option '-Wstrict-prototypes': option is valid for C/ObjC but not for C++")
                    self.compiler.compiler_so.remove('-Wstrict-prototypes')
                if self.compiler.compiler_cxx[0] != self.compiler.compiler[0] == self.compiler.compiler_so[0]:
                    info('replacing compiler {} by {}'.format(self.compiler.compiler_so[0], self.compiler.compiler_cxx[0]))
                    self.compiler.compiler_so[0] = self.compiler.compiler_cxx[0]
        if self.armhf:
            self.compiler.linker_so[0] = self.compiler.compiler_so[0]
        distutils.command.build_ext.build_ext.build_extension(self, extension)
        #HACK: Due to build_csrc.compare_file the C compiler may not run, even though
        #      the dependencies expect this. Therefore update the timestamp manually.
        ext_path = self.get_ext_fullpath(extension.name)
        try:
            os.utime(ext_path, None)
        except OSError:
            pass
        
        
class build_scripts (distutils.command.build_scripts.build_scripts):
    user_options = distutils.command.build_scripts.build_scripts.user_options + [
            ('inplace',     'i', 'ignore build-lib and put compiled modules into the source directory'),
            ('build-temp=', 't', "temporary build directory"),
        ]
    boolean_options = distutils.command.build.build.boolean_options + ['inplace']
    
    def initialize_options(self):
        distutils.command.build_scripts.build_scripts.initialize_options(self)
        self.inplace = None
        self.build_temp = None
        
    def finalize_options(self):
        distutils.command.build_scripts.build_scripts.finalize_options(self)
        self.set_undefined_options('build',
                    ('inplace', 'inplace'),
                    ('build_temp', 'build_temp'),
                )
                
    def run(self):
        build_dir = self.build_dir
        self.build_dir = self.build_temp
        distutils.command.build_scripts.build_scripts.run(self)
        self.build_dir = build_dir
        for script in self.scripts:
            outfile = os.path.basename(script)
            script = os.path.join(self.build_temp, outfile)
            if not os.path.exists(script):
                continue
            if not self.inplace:
                self.mkpath(self.build_dir)
                outfile = os.path.join(self.build_dir, outfile)
            outfile, ext = os.path.splitext(outfile)
            if ext != '.py':
                outfile += ext
            self.copy_file(script, outfile)
        
        
class build_models(Command):
    description = 'build data for Pybik models'
    user_options = [
        ('inplace',     'i', 'ignore build-lib and put compiled UI modules into the source '
                             'directory alongside your pure Python modules'),
        ('force',       'f', 'forcibly build everything (ignore file timestamps)'),
        ('parallel=',   None,'number of parallel build jobs or "True"'),
        ('pickle-protocol=', None, 'Pickle protocol for modeldata (default: highest protocol)'),
        ('maxsize=',    None, 'Maximum model size (default: 10)'),
        ('reproducible', None, 'Build modeldata reproducible (experimental)'),
        ('debugflags=', None, 'Comma-separated list of debug flags'),
    ]
    boolean_options = ['inplace', 'force', 'reproducible']
    
    def initialize_options(self):
        self.build_base = None
        self.inplace = None
        self.force = None
        self.parallel = None
        self.pickle_protocol = None
        self.maxsize = None
        self.reproducible = None
        self.debugflags = None
        
    def finalize_options(self):
        self.set_undefined_options('build',
                                       ('build_base', 'build_base'),
                                       ('inplace', 'inplace'),
                                       ('force', 'force'),
                                       ('parallel', 'parallel'),
                                       ('pickle_protocol', 'pickle_protocol'),
                                       ('maxsize', 'maxsize'),
                                       ('reproducible', 'reproducible'),
                                   )
        if isinstance(self.parallel, str):
            if self.parallel.lower() == 'true':
                self.parallel = True
            else:
                self.parallel = int(self.parallel)
        self.pickle_protocol = -1 if self.pickle_protocol is None else int(self.pickle_protocol)
        self.reproducible = False if self.reproducible is None else bool(self.reproducible)
        if self.debugflags is not None:
            import pybiklib.debug
            pybiklib.debug.set_flagstring(self.debugflags)
                
    def run(self):
        from buildlib import modeldata
        if self.maxsize is not None:
            from ast import literal_eval
            maxsize = literal_eval(self.maxsize)
            if isinstance(maxsize, tuple):
                modeldata.minsize, modeldata.maxsize = maxsize
            else:
                modeldata.maxsize = maxsize
        modeldir = 'data/models' if self.inplace else os.path.join(self.build_base, 'share', 'models')
        self.mkpath(modeldir)
        import pybiklib.debug
        prefix = 'f' if pybiklib.debug.DEBUG_MODELFAST else 'd'
        if self.force and not self.dry_run:
            for filename in os.listdir(modeldir):
                if filename[0] == prefix:
                    filename = os.path.join(modeldir, filename)
                    os.remove(filename)
        if self.force:
            testfunc = None
        else:
            testfunc = lambda filename: (any(newer(os.path.join('buildlib', f), filename) for f in
                                                ('modeldata.py', 'modeldef.py', 'geom.py')))
        if testfunc is None or testfunc(modeldata.get_indexfilename(modeldir)):
            message = 'generating model data, this may take some time'
            def create_modeldata():
                modeldata.create_modeldata(modeldir, testfunc, self.parallel,
                                        self.pickle_protocol, self.reproducible)
            self.execute(create_modeldata, [], msg=message)
        data_files = self.distribution.data_files
        if self.dry_run:
            return
        for filename in os.listdir(modeldir):
            sourcepath = os.path.join(modeldir, filename)
            data_files.append(('share/pybik/models', (sourcepath,)))
        
        
class build_appicons(Command):
    description = 'rename Pybik app-icons'
    user_options = [
        ('force',       'f', 'forcibly build everything (ignore file timestamps)'),
    ]
    boolean_options = ['inplace', 'force', 'reproducible']
    
    def initialize_options(self):
        self.build_base = None
        
    def finalize_options(self):
        self.set_undefined_options('build',
                                       ('build_base', 'build_base'),
                                   )
                
    def run(self):
        if self.dry_run:
            return
            
        sourcedir = 'data/icons'
        data_files = self.distribution.data_files
        for filename in os.listdir(sourcedir):
            size = str(int(filename.rsplit('-', 1)[1].split('.')[0]))
            sourcepath = os.path.join(sourcedir, filename)
            builddir = os.path.join(self.build_base, 'share', 'icons', size)
            self.mkpath(builddir)
            buildpath = os.path.join(builddir, 'pybik.png')
            self.copy_file(sourcepath, buildpath)
            data_files.append(('share/icons/hicolor/{0}x{0}/apps'.format(size), [buildpath]))
            
            
class build_i18n(Command):
    description = 'compile message catalog to binary format'
    
    user_options = [
        ('inplace',         'i',  'ignore build-lib and put compiled UI modules into the source '
                                  'directory alongside your pure Python modules'),
        ('force',           'f',  'forcibly build everything (ignore file timestamps)'),
    ]
        
    boolean_options = ['inplace', 'force']
    
    def initialize_options(self):
        self.build_base = None
        self.inplace = None
        self.force = None
        
    def finalize_options(self):
        self.set_undefined_options('build', ('build_base', 'build_base'),
                                            ('inplace', 'inplace'),
                                            ('force', 'force'),
                                  )
            
    def run(self):
        """Compile message catalog to binary format"""
        if self.inplace:
            mo_dir = 'data/locale'
        else:
            mo_dir = os.path.join(self.build_base, 'share', 'locale')
        data_files = self.distribution.data_files
        domain = self.distribution.metadata.name
        
        for lang in buildlib.utils.get_linguas():
            po_file = os.path.join('po', lang+'.po')
            mo_dir_lang =  os.path.join(mo_dir, lang, "LC_MESSAGES")
            mo_file = os.path.join(mo_dir_lang, "%s.mo" % domain)
            self.mkpath(mo_dir_lang)
            def msgfmt(po_file, mo_file):
                self.spawn(["msgfmt", po_file, "-o", mo_file])
            self.make_file([po_file], mo_file, msgfmt, [po_file, mo_file])
            
            targetpath = os.path.join("share/locale", lang, "LC_MESSAGES")
            data_files.append((targetpath, (mo_file,)))
            
            
class build_appmeta(Command):
    description = 'build translated desktop files'
    
    user_options = [
        ('inplace',  'i',  'ignore build-lib and put compiled UI modules into the source directory'),
        ('force',    'f',  'forcibly build everything (ignore file timestamps)'),
    ]
        
    boolean_options = ['inplace', 'force']
    
    def initialize_options(self):
        self.build_base = None
        self.inplace = None
        self.force = None
        
    def finalize_options(self):
        self.set_undefined_options('build', ('build_base', 'build_base'),
                                            ('inplace', 'inplace'),
                                            ('force', 'force'),
                                  )
        self.appmeta_dir = 'data/app-meta' if self.inplace else os.path.join(self.build_base, 'share',' app-meta')
            
    def run(self):
        from buildlib import create_docs
        
        if self.distribution.appmeta_templates is None:
            return
            
        data_files = self.distribution.data_files
        self.mkpath(self.appmeta_dir)
        
        def make_translated_file(src, tmp, dst, msgfmt_type):
            create_docs.create_doc(src, tmp, skip='deb')
            self.spawn(["msgfmt", msgfmt_type, '-d', 'po', '--template', tmp, '-o', dst])
            os.remove(tmp)
        for targetpath, template, template_depends in self.distribution.appmeta_templates:
            templatefile = create_docs.get_template_filename(template)
            tmpfile = os.path.join(self.appmeta_dir, os.path.basename(templatefile))
            dstfile = os.path.join(self.appmeta_dir, os.path.basename(template))
            if dstfile.endswith('.desktop'):
                msgfmt_type = '--desktop'
            elif dstfile.endswith('.appdata.xml'):
                msgfmt_type = '--xml'
            else:
                self.warn('Skipping %s, seems not to be a supported template.' % templatefile)
                continue
            self.make_file(glob('po/*.po') + [templatefile] + template_depends, dstfile,
                           make_translated_file, [template, tmpfile, dstfile, msgfmt_type])
            data_files.append((targetpath, [dstfile]))
        
        
class build_man (Command):
    description = 'build the manpage'
    user_options = [
        ('force',       'f',  'forcibly build everything (ignore file timestamps)'),    ]
    
    def initialize_options(self):
        self.force = None
        self.build_base = None
        
    def finalize_options(self):
        self.set_undefined_options('build', ('build_base', 'build_base'),
                                            ('force', 'force'),                                  )
        
    def run(self):
        from buildlib import create_docs
        
        def create_manpage(man_template, man_rst, man_file):
            man_template = os.path.splitext(man_template)[0]
            create_docs.create_doc(man_template, man_rst)
            self.spawn(['rst2man', man_rst, man_file])
            
        man_dir = os.path.join(self.build_base, 'share', 'man')
        self.mkpath(man_dir)
        tmp_dir = os.path.join(self.build_base, 'share', 'temp', 'rst')
        self.mkpath(tmp_dir)
        if self.dry_run:
            return
        for man_template in glob('data/templates/manpage_*.rst.in'):
            man_rst, unused_ext = os.path.splitext(os.path.basename(man_template))
            base, unused_ext = os.path.splitext(man_rst)
            unused, section, script = base.split('_', 2)
            man_rst = os.path.join(tmp_dir, man_rst)
            man_file = os.path.join(man_dir, '.'.join((script, section)))
            config.MANPAGE_SECTION = section
            self.make_file(['pybiklib/config.py', man_template], man_file,
                           create_manpage, [man_template, man_rst, man_file])
            self.distribution.data_files.append(('share/man/man'+section, [man_file]))
            
            
class build_doc (Command):
    description = "build documentation files"
    user_options = [
        ('inplace',         'i',  'ignore build-lib and put compiled UI modules into the source '
                                  'directory alongside your pure Python modules'),
    ]
    boolean_options = ['inplace']
    
    def initialize_options(self):
        self.inplace = None
        self.build_base = None
        self.doc_files = []
        
    def finalize_options(self):
        self.set_undefined_options('build', ('inplace', 'inplace'), ('build_base', 'build_base'))
        
    def run(self):
        from buildlib import create_docs
        
        info('generating documentation files')
        doc_dir = os.path.join(self.build_base, 'share', 'doc')
        self.mkpath(doc_dir)
        for basename, skip in [
                    ('copyright', 'deb'),
                    ('README', None),
                    ('INSTALL', None),
                ]:
            filename = None if self.inplace else os.path.join(doc_dir, basename)
            info('generating file: %r', filename or basename)
            create_docs.create_doc(basename, filename, skip=skip)
            if filename:
                self.doc_files.append(filename)
        
        
class install_lib (distutils.command.install_lib.install_lib):
    user_options = distutils.command.install_lib.install_lib.user_options + [
            ('arch-only', None, 'Install only architecture dependent files'),
            ('indep-only', None, 'Install only architecture independent files'),
        ]
    boolean_options = ['arch-only', 'indep-only']
    
    def initialize_options(self):
        distutils.command.install_lib.install_lib.initialize_options(self)
        self.data_dir = None
        self.arch_only = None
        self.indep_only = None
        
    def finalize_options(self):
        distutils.command.install_lib.install_lib.finalize_options(self)
        self.set_undefined_options('install',
                                       ('arch_only', 'arch_only'),
                                       ('indep_only', 'indep_only'),
                                   )
        if not self.arch_only and not self.indep_only:
            self.arch_only = self.indep_only = True
            
    def build(self):
        if not self.skip_build:
            if self.distribution.has_pure_modules() and self.indep_only:
                self.run_command('build_py')
            if self.distribution.has_ext_modules() and self.arch_only:
                self.run_command('build_ext')
                
                
class install_desktop(Command):
    description = 'install desktop files'
    user_options = [('ask', None, 'Ask before installation'),]
    boolean_options = ['ask']
    
    def initialize_options(self):
        self.ask = False
        
    def finalize_options(self):
        pass
    
    def run(self):
        def ask():
            try:
                answer = input('\nInstall desktop file? [Yn]: ')
            except EOFError:
                print()
                return False
            if answer.lower() not in ['y', '']:
                return False
            return True
                
        self.run_command('build_appmeta')
        appmeta_dir = self.get_finalized_command('build_appmeta').appmeta_dir
        target_dir = config.get_data_home('applications')
        
        def keyabspath(line, key, file=None):
            key += '='
            if line.startswith(key):
                if file is None:
                    file = line.split('=', 1)[1].rstrip()
                line = key + os.path.abspath(file) + '\n'
            return line
            
        self.mkpath(target_dir)
        for fnin in glob('%s/*.desktop' % appmeta_dir):
            fnout = os.path.join(target_dir, os.path.basename(fnin))
            tout = ''
            with open(fnin, 'rt', encoding='utf-8') as fin:
                for line in fin:
                    line = keyabspath(line, 'TryExec')
                    line = keyabspath(line, 'Exec')
                    line = keyabspath(line, 'Icon', config.APPICON192_FILE)
                    tout += line
            with suppress(FileNotFoundError), open(fnout, 'rt', encoding='utf-8') as fout:
                if tout == fout.read():
                    info('desktop file %r unchanged', fnout)
                    return
            if self.ask and not ask():
                return
            info('installing desktop file to: %s', fnout)
            with open(fnout, 'wt', encoding='utf-8') as fout:
                fout.write(tout)
                    
                    
class install (install_orig):
    user_options = install_orig.user_options + [
            ('data-dir=', 't', 'Directory where the application will find the data'),
            ('arch-only', None, 'Install only architecture dependent files'),
            ('indep-only', None, 'Install only architecture independent files'),
        ]
    boolean_options = ['arch-only', 'indep-only']
        
    def initialize_options(self):
        install_orig.initialize_options(self)
        self.data_dir = None
        self.arch_only = False
        self.indep_only = False
        
    def finalize_options(self):
        install_orig.finalize_options(self)
        if self.data_dir is None:
            self.data_dir = os.path.join(self.install_data, 'share')
        if not self.arch_only and not self.indep_only:
            self.arch_only = self.indep_only = True
        if not self.indep_only:
            self.__class__.sub_commands = [(cmd, func)
                            for cmd, func in install_orig.sub_commands
                                if cmd == 'install_lib']
            
    def run(self):
        if not self.skip_build:
            # install_orig.run() will run build, but we need
            # to modify a file between build and install
            build_cmd = self.distribution.get_command_obj('build')
            build_cmd.arch_only = self.arch_only
            build_cmd.indep_only = self.indep_only
            self.run_command('build')
            self.skip_build = True
            
        filename = os.path.join(self.build_lib, 'pybiklib', 'config.py')
        app_data_dir = os.path.join(self.data_dir, 'pybik')
        if self.indep_only:
            from buildlib.utils import modify_file
            modified = modify_file(filename, [
                    (r'^(data_dir\s*=\s*).*$',      r'\1' + repr(self.data_dir)),
                    (r'^(appdata_dir\s*=\s*).*$',   r'\1' + repr(app_data_dir)),])
            if not modified:
                warn('unchanged file: %r', filename)
        install_orig.run(self)
        
        
class clean (distutils.command.clean.clean):
    user_options = distutils.command.clean.clean.user_options + [
            ('inplace', 'i',         'clean up files in the source directory'),
        ]
    boolean_options = distutils.command.clean.clean.boolean_options + [
            'inplace',
        ]
        
    def initialize_options(self):
        distutils.command.clean.clean.initialize_options(self)
        self.inplace = None
        
    def finalize_options(self):
        distutils.command.clean.clean.finalize_options(self)
        self.set_undefined_options('build',
                                       ('inplace', 'inplace'),
                                   )
        
    def run(self):
        def remove_tree_(directory):
            if os.path.exists(directory):
                remove_tree(directory, dry_run=self.dry_run, verbose=self.verbose)
            else:
                warn("%r does not exist -- can't clean it", directory)
        def remove_file(filename):
            if os.path.exists(filename):
                info('removing %r', filename)
                if not self.dry_run:
                    os.remove(filename)
            else:
                warn("%r does not exist -- can't clean it", filename)
                
        if self.all:
            remove_tree_(os.path.join(self.build_base, 'share'))
        distutils.command.clean.clean.run(self)
        remove_file('MANIFEST')
        if self.inplace:
            for dirname in ('buildlib/', 'pybiklib/', 'pybiklib/ext/', 'pybiklib/plugins/', 'tools/'):
                remove_tree_(dirname + '__pycache__')
            if self.all:
                dirnames = ['data/app-meta', 'data/locale', 'data/models']
                for dirname in dirnames:
                    remove_tree_(dirname)
                for filename in glob('pybiklib/ext/*.so'):
                    remove_file(filename)
                for filename in ['copyright', 'INSTALL', 'README', 'pybik']:
                    remove_file(filename)
                
                
class sdist(distutils.command.sdist.sdist):
    user_options = distutils.command.sdist.sdist.user_options + [
            ('debian-names', None,  'Create archive files with Debian names'),
        ]
    boolean_options = distutils.command.sdist.sdist.boolean_options + [
            'debian-names'
        ]
        
    def initialize_options(self):
        distutils.command.sdist.sdist.initialize_options(self)
        self.debian_names = False
        self.appmeta_dir = None
        self.distribution.get_option_dict('build_csrc')['variants'] = ('class sdist', 'ogl,es2')
        
    def finalize_options(self):
        distutils.command.sdist.sdist.finalize_options(self)
        self.set_undefined_options('build_appmeta', ('appmeta_dir', 'appmeta_dir'))
        
    def run(self):
        self.run_command('build_doc')
        self.run_command('build_csrc')
        self.run_command('build_appmeta')
        distutils.command.sdist.sdist.run(self)
        
        for archive_file in self.archive_files:
            if self.debian_names:
                debian_file = archive_file.replace('-', '_', 1).replace('.tar', '.orig.tar', 1)
                os.rename(archive_file, debian_file)
        
    def get_file_list(self):
        for lang in buildlib.utils.get_linguas():
            self.filelist.files.append(os.path.join('po', lang+'.po'))
        distutils.command.sdist.sdist.get_file_list(self)
        
    def make_release_tree(self, base_dir, files):
        distutils.command.sdist.sdist.make_release_tree(self, base_dir, files)
        # doc files
        for filename in self.get_finalized_command('build_doc').doc_files:
            self.copy_file(filename, base_dir)
        # csrc
        dst_dir = os.path.join(base_dir, 'csrc')
        self.mkpath(dst_dir)
        cc_files = self.get_finalized_command('build_csrc').cc_files
        for src in cc_files:
            self.copy_file(src, dst_dir)
        # desktop- and appdata-files
        if self.distribution.appmeta_templates is not None:
            dst_dir = os.path.join(base_dir, 'data/app-meta')
            self.mkpath(dst_dir)
            for unused_targetpath, template, unused_template_depends in self.distribution.appmeta_templates:
                src = os.path.join(self.appmeta_dir, template)
                self.copy_file(src, dst_dir)
        #
        dst_dir = os.path.join(base_dir, 'po')
        self.mkpath(dst_dir)
        self.copy_file('po/LINGUAS', dst_dir)
        
        
variantgroups_by_module = {
                        '_debug': ('glvariant',),
                        '_gldraw': ('glvariant', 'gldebug'),
                        '_glarea': ('glvariant', 'gldebug', 'offscreen'),
                        '_qtui': (),
                        '_qt': ('qtvariant', 'glvariant', 'gldebug', 'offscreen'),
                        '_qtexec': (),
                     }
                    
scripts = ['pybiklib/pybik.py']
data_files = [
                ('share/pybik/ui/cursors', glob('data/ui/cursors/*')),
                ('share/pybik/ui/images', glob('data/ui/images/*')),
                ('share/pybik/ui/qt', glob('data/ui/qt/*.*')),
                ('share/pybik/ui/qt/images', glob('data/ui/qt/images/*')),
                ('share/pybik/ui/thumbnails', glob('data/ui/thumbnails/*')),
                ('share/pybik/plugins', glob('data/plugins/*')),
                ('share/pybik/shaders', glob('data/shaders/*')),
                ('share/pybik/', ['data/GPL-3']),
            ]
appmeta_templates = [ ('share/applications', 'pybik.desktop', []),
                      ('share/metainfo', 'net.launchpad.pybik.appdata.xml', ['NEWS']),
                    ]
variant_libraries = {
                'GL': {'ogl': ['GL'],
                       'es2': ['GLESv2']},
                'Qt': {'qtw': ['Qt5Core', 'Qt5Gui', 'Qt5Widgets'],
                       'qtq': ['Qt5Core', 'Qt5Gui', 'Qt5Widgets', 'Qt5Qml', 'Qt5Quick']},
            }
ext_modules = [
                Extension('pybiklib/ext/_debug',
                            glob('pybiklib/ext/_debug_*.pyx'),
                            depends=glob('pybiklib/ext/gl_*.pxd')+glob('pybiklib/ext/_debug_*.pxd'),
                            libraries=['GL'],
                            define_macros=[('GL_GLEXT_PROTOTYPES', None)]),
                Extension('pybiklib/ext/_gldraw',
                            ['pybiklib/ext/gldraw.py'],
                            depends=glob('pybiklib/ext/gl_*.pxd'),
                            libraries=['GL'],
                            define_macros=[('GL_GLEXT_PROTOTYPES', None)]),
                Extension('pybiklib/ext/_glarea',
                            ["pybiklib/ext/glarea.py"],
                            depends=glob('pybiklib/ext/gl_*.pxd')+['pybiklib/ext/gldraw.py'],
                            libraries=['GL'],
                            define_macros=[('GL_GLEXT_PROTOTYPES', None)]),
                Extension('pybiklib/ext/_qtui',
                            ["pybiklib/ext/qtui.py"],
                            depends=['pybiklib/ext/qt.pxd', 'pybiklib/ext/qtui_p.h'] + glob('data/ui/qt/*.ui'),
                            language='c++',
                            include_dirs=[sysconfig.get_config_var('INCLUDEDIR')+'/qt5',
                                          sysconfig.get_config_var('INCLUDEDIR')+'/<multiarchsubdir>/qt5'],
                            libraries=['Qt5Core', 'Qt5Gui', 'Qt5Widgets']),
                Extension('pybiklib/ext/_qt',
                            ["pybiklib/ext/qt.py"],
                            depends=glob('pybiklib/ext/gl_*.pxd')+[
                                     'pybiklib/ext/qt.pxd', 'pybiklib/ext/qtq.pxd',
                                     'pybiklib/ext/glarea.py', 'pybiklib/ext/qtui.py'],
                            language='c++',
                            include_dirs=[sysconfig.get_config_var('INCLUDEDIR')+'/qt5',
                                          sysconfig.get_config_var('INCLUDEDIR')+'/<multiarchsubdir>/qt5'],
                            libraries=['Qt']),
                Extension('pybiklib/ext/_qtexec',
                            ["pybiklib/ext/qtexec.py"],
                            depends=['pybiklib/ext/qt.pxd'],
                            language='c++',
                            include_dirs=[sysconfig.get_config_var('INCLUDEDIR')+'/qt5',
                                          sysconfig.get_config_var('INCLUDEDIR')+'/<multiarchsubdir>/qt5'],
                            libraries=['Qt5Core', 'Qt5Gui', 'Qt5Widgets']),
            ]
packages = ['pybiklib', 'pybiklib.ext', 'pybiklib.plugins']

cmdclass = {
            'build': build,
            'build_csrc': build_csrc,
            'build_ext': build_ext,
            'build_scripts': build_scripts,
            'build_models': build_models,
            'build_appicons': build_appicons,
            'build_i18n': build_i18n,
            'build_appmeta': build_appmeta,
            'build_man': build_man,
            'build_doc': build_doc,
            'install_data': distutils.command.install_data.install_data,
            'install_lib': install_lib,
            'install_desktop': install_desktop,
            'install': install,
            'clean': clean,
            'sdist': sdist,
        }

setup(
        # Metadata
        name=config.PACKAGE,
        version=config.VERSION,
        author=config.AUTHOR,
        author_email=config.CONTACT_EMAIL,
        url=config.WEBSITE,
        download_url=config.DOWNLOADS,
        
        description=config.SHORT_DESCRIPTION,
        long_description='\n'.join(config.LONG_DESCRIPTION),
        classifiers=[
                'Development Status :: 5 - Production/Stable',
                'Environment :: X11 Applications :: Qt',
                'Intended Audience :: End Users/Desktop',
                'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
                'Operating System :: POSIX',
                'Operating System :: POSIX :: Linux',
                'Programming Language :: Python :: 3',
                'Programming Language :: Python :: 3.4',
                'Programming Language :: Cython',
                #TODO: 'Programming Language :: OpenGL Shading Language',
                'Topic :: Games/Entertainment :: Puzzle Games',
            ],
        keywords='rubik cube puzzle game',
        license=config.LICENSE_NAME,
        # Metadata added in overloaded Distribution
        bug_contact=config.CONTACT_FILEBUG,
        
        # Files
        scripts=scripts,
        data_files=data_files,
        appmeta_templates=appmeta_templates,
        ext_modules=ext_modules,
        packages=packages,
        
        # setup classes
        distclass=Distribution,
        cmdclass=cmdclass,
     )
     
