from distutils import log
from distutils.command.build_ext import build_ext
from distutils.errors import DistutilsError, CCompilerError, CompileError
from distutils.util import strtobool

from configparser import ConfigParser


class build_optional_ext(build_ext):
    """This is a version of the build_ext command that allows specific
    extensions to be marked as 'optional'.  If an optional extension fails to
    build, a warning message is displayed but the build does not cancel.

    It should be noted that this functionality already exists in the Python3
    versions of distutils and packaging, but we must provide backwards
    compatibility for it.
    """

    command_name = 'build_ext'

    def _find_optional_extensions(self, extensions):
        """Reads the setup.cfg to determine which extensions were set as
        'optional'.  Optionally, developers may also provide a warning message
        to display (otherwise a default message is used).  This is one way in
        which this command improves on the existing functionality on Python 3.
        """

        # TODO: However, support for the 'optional' extension attribute should
        # be included in d2to1, since it's a legitimate backwards compatibility
        # issue.  But until I do another release it will have to be supported
        # here.

        cfg = ConfigParser()
        try:
            cfg.read('setup.cfg')
        except Exception as e:
            log.warn('Failed to read setup.cfg: %s; proceeding as though '
                     'there are no optional extensions' % e)
            return

        # Map extension names to extension objects
        extensions = dict((ext.name, ext) for ext in extensions)

        for section in cfg.sections():
            if not section.startswith('extension='):
                continue

            # The extension name can be specified with the 'name' option, but
            # if that's missing the name is taken from the section header for
            # now
            if cfg.has_option(section, 'name'):
                name = cfg.get(section, 'name')
            else:
                _, name = section.split('=', 1)

            if name not in extensions:
                # Could happen, for example, if a setup_hook disabled some
                # extensions
                continue

            ext = extensions[name]

            if cfg.has_option(section, 'optional'):
                ext._optional = strtobool(cfg.get(section, 'optional'))
            else:
                ext._optional = False

            if cfg.has_option(section, 'fail_message'):
                ext._fail_message = cfg.get(section, 'fail_message')

    def check_extensions_list(self, extensions):
        build_ext.check_extensions_list(self, extensions)
        self._find_optional_extensions(extensions)

    def build_extension(self, ext):
        try:
            build_ext.build_extension(self, ext)
        except (CCompilerError, DistutilsError, CompileError) as e:
            if not hasattr(ext, '_optional') or not ext._optional:
                raise
            log.warn('building optional extension "%s" failed: %s' %
                     (ext.name, e))
            if hasattr(ext, '_fail_message'):
                log.warn(ext._fail_message)
