aboutsummaryrefslogtreecommitdiffstats
path: root/meta/files
AgeCommit message (Expand)Author
2017-04-19ext-sdk-prepare.py: use quiet mode when preparing sysrootPaul Eggleton
2017-03-17fs-perms.txt: Two cleanup tweaks for consistency.Robert P. J. Day
2017-03-10toolchain-shar-extract.sh: Ensure sbin directories are in PATH in clean envir...Richard Purdie
2017-02-28kernel, license, sstate, rootfs.py: Remove deploy directory READMEMike Crowe
2017-01-31toolchain-shar-extract: compare SDK and host gcc versionsEd Bartosh
2017-01-23Switch to Recipe Specific SysrootsRichard Purdie
2016-10-11sdk-installer: Fix unclear SDK installer messageTodor Minchev
2016-09-23toolchain-shar-relocate.sh: Fix typoRichard Purdie
2016-09-22toolchain-shar-relocate.sh: Add error-handlingMariano Lopez
2016-08-17classes/populate_sdk_ext: drop duplicated error messagePaul Eggleton
2016-08-17classes/populate_sdk_ext: add some pre-install checksPaul Eggleton
2016-08-17toolchain-shar-extract.sh: add option to list contentsPaul Eggleton
2016-07-25classes/populate_sdk_ext: show progress when preparing build systemPaul Eggleton
2016-07-07classes/populate_sdk_ext: eliminate double execution on installPaul Eggleton
2016-07-07toolchain-shar-extract.sh: allow TERM through into SDK installer envPaul Eggleton
2016-06-02scripts: Replace basestring -> str for python3Ed Bartosh
2016-05-21bitbake.conf/toolchain-shar-extract: Use en_US.UTF-8 as localeRichard Purdie
2016-04-18ext-sdk-prepare.py: exclude do_rm_work from unexpected output; create unit testBill Randle
2016-04-15fs-perms.txt: fix ROOT_HOME's permissionRobert Yang
2016-04-15Revert "fs-perms.txt: fix ROOT_HOME's permission"Ross Burton
2016-04-14fs-perms.txt: fix ROOT_HOME's permissionRobert Yang
2016-03-31toolchain-shar-extract.sh: ensure all_proxy is allowed throughPaul Eggleton
2016-03-31oe-publish-sdk: exclude sstate-cache if publishing minimal SDKPaul Eggleton
2016-03-31scripts, lib: Don't limit traceback lengths to arbitrary valuesRichard Purdie
2016-03-28toolchain-shar-extract.sh: check the length for target_sdk_dirWenlin Kang
2016-03-23classes/populate_sdk_ext: parse metadata on minimal SDK installPaul Eggleton
2016-03-20ParaTypeFFL-1.3: Add license fileIoan-Adrian Ratiu
2016-03-11meta: toolchain-shar-relocate.sh: Fix for extracting SDK in the same director...Zawadzki, Krzysztof (Nokia - PL/Wroclaw)
2016-03-06ext-sdk-prepare: Catch setscene tasks which should have run but didn'tRichard Purdie
2016-02-10toolchain-shar-extract.sh: Add proxy variable to new env.Aníbal Limón
2016-01-22toolchain-shar-extract.sh: improve behaviour when xz is not installedPaul Eggleton
2016-01-22meta/files/toolchain-shar-relocate.sh: Detect different python binaries and s...Liam R. Howlett
2016-01-13classes/populate_sdk_ext: check that extensible SDK prepared correctlyPaul Eggleton
2016-01-11populate_sdk: Switch from bzip2 to xzRichard Purdie
2016-01-11populate_sdk_ext: Use new --setscene-only option to bitbake instead of workar...Richard Purdie
2015-12-11toolchain-shar-extract.sh: fix ~ not working in pathPaul Eggleton
2015-12-08toolchain-shar-extract.sh: ensure cleaned environment will work for ext SDKPaul Eggleton
2015-12-08toolchain-shar-extract.sh: proper fix for additional env setup scriptsPaul Eggleton
2015-12-01toolchain-shar-extract.sh: Ensure it's ran in clean environmentGeorge Nita
2015-12-01toolchain-shar-extract.sh: do not allow $ in paths for ext SDKPaul Eggleton
2015-12-01toolchain-shar-relocate: don't assume last state of env_setup_script is goodJean-Francois Dagenais
2015-10-20toolchain-shar-extract.sh: print full-length title underlinePaul Eggleton
2015-10-20classes/populate_sdk_ext: prevent image construction from executing on installPaul Eggleton
2015-10-19toolchain-shar-extract.sh: provide proper path for env_setup_scriptMaxin B. John
2015-10-19common-licenses: use correct GFDL-1.1 license textPaul Eggleton
2015-09-29toolchain-shar-extract: Correct environment-setup script names for multilibRichard Purdie
2015-09-29toolchain-shar-relocate.sh: make it fasterRobert Yang
2015-09-24toolchain-shar-extract.sh: remove checkbashismRobert Yang
2015-09-22toolchain-shar-extract.sh: show progress when extracting SDKPaul Eggleton
2015-09-07Extensible SDK: allow for installation without preparing build systemQi.Chen@windriver.com
id='n830' href='#n830'>830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
# Recipe creation tool - create command build system handlers
#
# Copyright (C) 2014-2016 Intel Corporation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import re
import logging
import glob
from recipetool.create import RecipeHandler, validate_pv

logger = logging.getLogger('recipetool')

tinfoil = None
plugins = None

def plugin_init(pluginlist):
    # Take a reference to the list so we can use it later
    global plugins
    plugins = pluginlist

def tinfoil_init(instance):
    global tinfoil
    tinfoil = instance


class CmakeRecipeHandler(RecipeHandler):
    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
        if 'buildsystem' in handled:
            return False

        if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']):
            classes.append('cmake')
            values = CmakeRecipeHandler.extract_cmake_deps(lines_before, srctree, extravalues)
            classes.extend(values.pop('inherit', '').split())
            for var, value in values.items():
                lines_before.append('%s = "%s"' % (var, value))
            lines_after.append('# Specify any options you want to pass to cmake using EXTRA_OECMAKE:')
            lines_after.append('EXTRA_OECMAKE = ""')
            lines_after.append('')
            handled.append('buildsystem')
            return True
        return False

    @staticmethod
    def extract_cmake_deps(outlines, srctree, extravalues, cmakelistsfile=None):
        # Find all plugins that want to register handlers
        logger.debug('Loading cmake handlers')
        handlers = []
        for plugin in plugins:
            if hasattr(plugin, 'register_cmake_handlers'):
                plugin.register_cmake_handlers(handlers)

        values = {}
        inherits = []

        if cmakelistsfile:
            srcfiles = [cmakelistsfile]
        else:
            srcfiles = RecipeHandler.checkfiles(srctree, ['CMakeLists.txt'])

        # Note that some of these are non-standard, but probably better to
        # be able to map them anyway if we see them
        cmake_pkgmap = {'alsa': 'alsa-lib',
                        'aspell': 'aspell',
                        'atk': 'atk',
                        'bison': 'bison-native',
                        'boost': 'boost',
                        'bzip2': 'bzip2',
                        'cairo': 'cairo',
                        'cups': 'cups',
                        'curl': 'curl',
                        'curses': 'ncurses',
                        'cvs': 'cvs',
                        'drm': 'libdrm',
                        'dbus': 'dbus',
                        'dbusglib': 'dbus-glib',
                        'egl': 'virtual/egl',
                        'expat': 'expat',
                        'flex': 'flex-native',
                        'fontconfig': 'fontconfig',
                        'freetype': 'freetype',
                        'gettext': '',
                        'git': '',
                        'gio': 'glib-2.0',
                        'giounix': 'glib-2.0',
                        'glew': 'glew',
                        'glib': 'glib-2.0',
                        'glib2': 'glib-2.0',
                        'glu': 'libglu',
                        'glut': 'freeglut',
                        'gobject': 'glib-2.0',
                        'gperf': 'gperf-native',
                        'gnutls': 'gnutls',
                        'gtk2': 'gtk+',
                        'gtk3': 'gtk+3',
                        'gtk': 'gtk+3',
                        'harfbuzz': 'harfbuzz',
                        'icu': 'icu',
                        'intl': 'virtual/libintl',
                        'jpeg': 'jpeg',
                        'libarchive': 'libarchive',
                        'libiconv': 'virtual/libiconv',
                        'liblzma': 'xz',
                        'libxml2': 'libxml2',
                        'libxslt': 'libxslt',
                        'opengl': 'virtual/libgl',
                        'openmp': '',
                        'openssl': 'openssl',
                        'pango': 'pango',
                        'perl': '',
                        'perllibs': '',
                        'pkgconfig': '',
                        'png': 'libpng',
                        'pthread': '',
                        'pythoninterp': '',
                        'pythonlibs': '',
                        'ruby': 'ruby-native',
                        'sdl': 'libsdl',
                        'sdl2': 'libsdl2',
                        'subversion': 'subversion-native',
                        'swig': 'swig-native',
                        'tcl': 'tcl-native',
                        'threads': '',
                        'tiff': 'tiff',
                        'wget': 'wget',
                        'x11': 'libx11',
                        'xcb': 'libxcb',
                        'xext': 'libxext',
                        'xfixes': 'libxfixes',
                        'zlib': 'zlib',
                        }

        pcdeps = []
        libdeps = []
        deps = []
        unmappedpkgs = []

        proj_re = re.compile('project\s*\(([^)]*)\)', re.IGNORECASE)
        pkgcm_re = re.compile('pkg_check_modules\s*\(\s*[a-zA-Z0-9-_]+\s*(REQUIRED)?\s+([^)\s]+)\s*\)', re.IGNORECASE)
        pkgsm_re = re.compile('pkg_search_module\s*\(\s*[a-zA-Z0-9-_]+\s*(REQUIRED)?((\s+[^)\s]+)+)\s*\)', re.IGNORECASE)
        findpackage_re = re.compile('find_package\s*\(\s*([a-zA-Z0-9-_]+)\s*.*', re.IGNORECASE)
        findlibrary_re = re.compile('find_library\s*\(\s*[a-zA-Z0-9-_]+\s*(NAMES\s+)?([a-zA-Z0-9-_ ]+)\s*.*')
        checklib_re = re.compile('check_library_exists\s*\(\s*([^\s)]+)\s*.*', re.IGNORECASE)
        include_re = re.compile('include\s*\(\s*([^)\s]*)\s*\)', re.IGNORECASE)
        subdir_re = re.compile('add_subdirectory\s*\(\s*([^)\s]*)\s*([^)\s]*)\s*\)', re.IGNORECASE)
        dep_re = re.compile('([^ ><=]+)( *[<>=]+ *[^ ><=]+)?')

        def find_cmake_package(pkg):
            RecipeHandler.load_devel_filemap(tinfoil.config_data)
            for fn, pn in RecipeHandler.recipecmakefilemap.items():
                splitname = fn.split('/')
                if len(splitname) > 1:
                    if splitname[0].lower().startswith(pkg.lower()):
                        if splitname[1] == '%s-config.cmake' % pkg.lower() or splitname[1] == '%sConfig.cmake' % pkg or splitname[1] == 'Find%s.cmake' % pkg:
                            return pn
            return None

        def interpret_value(value):
            return value.strip('"')

        def parse_cmake_file(fn, paths=None):
            searchpaths = (paths or []) + [os.path.dirname(fn)]
            logger.debug('Parsing file %s' % fn)
            with open(fn, 'r', errors='surrogateescape') as f:
                for line in f:
                    line = line.strip()
                    for handler in handlers:
                        if handler.process_line(srctree, fn, line, libdeps, pcdeps, deps, outlines, inherits, values):
                            continue
                    res = include_re.match(line)
                    if res:
                        includefn = bb.utils.which(':'.join(searchpaths), res.group(1))
                        if includefn:
                            parse_cmake_file(includefn, searchpaths)
                        else:
                            logger.debug('Unable to recurse into include file %s' % res.group(1))
                        continue
                    res = subdir_re.match(line)
                    if res:
                        subdirfn = os.path.join(os.path.dirname(fn), res.group(1), 'CMakeLists.txt')
                        if os.path.exists(subdirfn):
                            parse_cmake_file(subdirfn, searchpaths)
                        else:
                            logger.debug('Unable to recurse into subdirectory file %s' % subdirfn)
                        continue
                    res = proj_re.match(line)
                    if res:
                        extravalues['PN'] = interpret_value(res.group(1).split()[0])
                        continue
                    res = pkgcm_re.match(line)
                    if res:
                        res = dep_re.findall(res.group(2))
                        if res:
                            pcdeps.extend([interpret_value(x[0]) for x in res])
                        inherits.append('pkgconfig')
                        continue
                    res = pkgsm_re.match(line)
                    if res:
                        res = dep_re.findall(res.group(2))
                        if res:
                            # Note: appending a tuple here!
                            item = tuple((interpret_value(x[0]) for x in res))
                            if len(item) == 1:
                                item = item[0]
                            pcdeps.append(item)
                        inherits.append('pkgconfig')
                        continue
                    res = findpackage_re.match(line)
                    if res:
                        origpkg = res.group(1)
                        pkg = interpret_value(origpkg)
                        found = False
                        for handler in handlers:
                            if handler.process_findpackage(srctree, fn, pkg, deps, outlines, inherits, values):
                                logger.debug('Mapped CMake package %s via handler %s' % (pkg, handler.__class__.__name__))
                                found = True
                                break
                        if found:
                            continue
                        elif pkg == 'Gettext':
                            inherits.append('gettext')
                        elif pkg == 'Perl':
                            inherits.append('perlnative')
                        elif pkg == 'PkgConfig':
                            inherits.append('pkgconfig')
                        elif pkg == 'PythonInterp':
                            inherits.append('pythonnative')
                        elif pkg == 'PythonLibs':
                            inherits.append('python-dir')
                        else:
                            # Try to map via looking at installed CMake packages in pkgdata
                            dep = find_cmake_package(pkg)
                            if dep:
                                logger.debug('Mapped CMake package %s to recipe %s via pkgdata' % (pkg, dep))
                                deps.append(dep)
                            else:
                                dep = cmake_pkgmap.get(pkg.lower(), None)
                                if dep:
                                    logger.debug('Mapped CMake package %s to recipe %s via internal list' % (pkg, dep))
                                    deps.append(dep)
                                elif dep is None:
                                    unmappedpkgs.append(origpkg)
                        continue
                    res = checklib_re.match(line)
                    if res:
                        lib = interpret_value(res.group(1))
                        if not lib.startswith('$'):
                            libdeps.append(lib)
                    res = findlibrary_re.match(line)
                    if res:
                        libs = res.group(2).split()
                        for lib in libs:
                            if lib in ['HINTS', 'PATHS', 'PATH_SUFFIXES', 'DOC', 'NAMES_PER_DIR'] or lib.startswith(('NO_', 'CMAKE_', 'ONLY_CMAKE_')):
                                break
                            lib = interpret_value(lib)
                            if not lib.startswith('$'):
                                libdeps.append(lib)
                    if line.lower().startswith('useswig'):
                        deps.append('swig-native')
                        continue

        parse_cmake_file(srcfiles[0])

        if unmappedpkgs:
            outlines.append('# NOTE: unable to map the following CMake package dependencies: %s' % ' '.join(list(set(unmappedpkgs))))

        RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)

        for handler in handlers:
            handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values)

        if inherits:
            values['inherit'] = ' '.join(list(set(inherits)))

        return values


class CmakeExtensionHandler(object):
    '''Base class for CMake extension handlers'''
    def process_line(self, srctree, fn, line, libdeps, pcdeps, deps, outlines, inherits, values):
        '''
        Handle a line parsed out of an CMake file.
        Return True if you've completely handled the passed in line, otherwise return False.
        '''
        return False

    def process_findpackage(self, srctree, fn, pkg, deps, outlines, inherits, values):
        '''
        Handle a find_package package parsed out of a CMake file.
        Return True if you've completely handled the passed in package, otherwise return False.
        '''
        return False

    def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values):
        '''
        Apply any desired post-processing on the output
        '''
        return



class SconsRecipeHandler(RecipeHandler):
    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
        if 'buildsystem' in handled:
            return False

        if RecipeHandler.checkfiles(srctree, ['SConstruct', 'Sconstruct', 'sconstruct']):
            classes.append('scons')
            lines_after.append('# Specify any options you want to pass to scons using EXTRA_OESCONS:')
            lines_after.append('EXTRA_OESCONS = ""')
            lines_after.append('')
            handled.append('buildsystem')
            return True
        return False


class QmakeRecipeHandler(RecipeHandler):
    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
        if 'buildsystem' in handled:
            return False

        if RecipeHandler.checkfiles(srctree, ['*.pro']):
            classes.append('qmake2')
            handled.append('buildsystem')
            return True
        return False


class AutotoolsRecipeHandler(RecipeHandler):
    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
        if 'buildsystem' in handled:
            return False

        autoconf = False
        if RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']):
            autoconf = True
            values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, extravalues)
            classes.extend(values.pop('inherit', '').split())
            for var, value in values.items():
                lines_before.append('%s = "%s"' % (var, value))
        else:
            conffile = RecipeHandler.checkfiles(srctree, ['configure'])
            if conffile:
                # Check if this is just a pre-generated autoconf configure script
                with open(conffile[0], 'r', errors='surrogateescape') as f:
                    for i in range(1, 10):
                        if 'Generated by GNU Autoconf' in f.readline():
                            autoconf = True
                            break

        if autoconf and not ('PV' in extravalues and 'PN' in extravalues):
            # Last resort
            conffile = RecipeHandler.checkfiles(srctree, ['configure'])
            if conffile:
                with open(conffile[0], 'r', errors='surrogateescape') as f:
                    for line in f:
                        line = line.strip()
                        if line.startswith('VERSION=') or line.startswith('PACKAGE_VERSION='):
                            pv = line.split('=')[1].strip('"\'')
                            if pv and not 'PV' in extravalues and validate_pv(pv):
                                extravalues['PV'] = pv
                        elif line.startswith('PACKAGE_NAME=') or line.startswith('PACKAGE='):
                            pn = line.split('=')[1].strip('"\'')
                            if pn and not 'PN' in extravalues:
                                extravalues['PN'] = pn

        if autoconf:
            lines_before.append('')
            lines_before.append('# NOTE: if this software is not capable of being built in a separate build directory')
            lines_before.append('# from the source, you should replace autotools with autotools-brokensep in the')
            lines_before.append('# inherit line')
            classes.append('autotools')
            lines_after.append('# Specify any options you want to pass to the configure script using EXTRA_OECONF:')
            lines_after.append('EXTRA_OECONF = ""')
            lines_after.append('')
            handled.append('buildsystem')
            return True

        return False

    @staticmethod
    def extract_autotools_deps(outlines, srctree, extravalues=None, acfile=None):
        import shlex

        # Find all plugins that want to register handlers
        logger.debug('Loading autotools handlers')
        handlers = []
        for plugin in plugins:
            if hasattr(plugin, 'register_autotools_handlers'):
                plugin.register_autotools_handlers(handlers)

        values = {}
        inherits = []

        # Hardcoded map, we also use a dynamic one based on what's in the sysroot
        progmap = {'flex': 'flex-native',
                'bison': 'bison-native',
                'm4': 'm4-native',
                'tar': 'tar-native',
                'ar': 'binutils-native',
                'ranlib': 'binutils-native',
                'ld': 'binutils-native',
                'strip': 'binutils-native',
                'libtool': '',
                'autoconf': '',
                'autoheader': '',
                'automake': '',
                'uname': '',
                'rm': '',
                'cp': '',
                'mv': '',
                'find': '',
                'awk': '',
                'sed': '',
                }
        progclassmap = {'gconftool-2': 'gconf',
                'pkg-config': 'pkgconfig',
                'python': 'pythonnative',
                'python3': 'python3native',
                'perl': 'perlnative',
                'makeinfo': 'texinfo',
                }

        pkg_re = re.compile('PKG_CHECK_MODULES\(\s*\[?[a-zA-Z0-9_]*\]?,\s*\[?([^,\]]*)\]?[),].*')
        pkgce_re = re.compile('PKG_CHECK_EXISTS\(\s*\[?([^,\]]*)\]?[),].*')
        lib_re = re.compile('AC_CHECK_LIB\(\s*\[?([^,\]]*)\]?,.*')
        libx_re = re.compile('AX_CHECK_LIBRARY\(\s*\[?[^,\]]*\]?,\s*\[?([^,\]]*)\]?,\s*\[?([a-zA-Z0-9-]*)\]?,.*')
        progs_re = re.compile('_PROGS?\(\s*\[?[a-zA-Z0-9_]*\]?,\s*\[?([^,\]]*)\]?[),].*')
        dep_re = re.compile('([^ ><=]+)( [<>=]+ [^ ><=]+)?')
        ac_init_re = re.compile('AC_INIT\(\s*([^,]+),\s*([^,]+)[,)].*')
        am_init_re = re.compile('AM_INIT_AUTOMAKE\(\s*([^,]+),\s*([^,]+)[,)].*')
        define_re = re.compile('\s*(m4_)?define\(\s*([^,]+),\s*([^,]+)\)')
        version_re = re.compile('([0-9.]+)')

        defines = {}
        def subst_defines(value):
            newvalue = value
            for define, defval in defines.items():
                newvalue = newvalue.replace(define, defval)
            if newvalue != value:
                return subst_defines(newvalue)
            return value

        def process_value(value):
            value = value.replace('[', '').replace(']', '')
            if value.startswith('m4_esyscmd(') or value.startswith('m4_esyscmd_s('):
                cmd = subst_defines(value[value.index('(')+1:-1])
                try:
                    if '|' in cmd:
                        cmd = 'set -o pipefail; ' + cmd
                    stdout, _ = bb.process.run(cmd, cwd=srctree, shell=True)
                    ret = stdout.rstrip()
                except bb.process.ExecutionError as e:
                    ret = ''
            elif value.startswith('m4_'):
                return None
            ret = subst_defines(value)
            if ret:
                ret = ret.strip('"\'')
            return ret

        # Since a configure.ac file is essentially a program, this is only ever going to be
        # a hack unfortunately; but it ought to be enough of an approximation
        if acfile:
            srcfiles = [acfile]
        else:
            srcfiles = RecipeHandler.checkfiles(srctree, ['acinclude.m4', 'configure.ac', 'configure.in'])

        pcdeps = []
        libdeps = []
        deps = []
        unmapped = []

        RecipeHandler.load_binmap(tinfoil.config_data)

        def process_macro(keyword, value):
            for handler in handlers:
                if handler.process_macro(srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values):
                    return
            logger.debug('Found keyword %s with value "%s"' % (keyword, value))
            if keyword == 'PKG_CHECK_MODULES':
                res = pkg_re.search(value)
                if res:
                    res = dep_re.findall(res.group(1))
                    if res:
                        pcdeps.extend([x[0] for x in res])
                inherits.append('pkgconfig')
            elif keyword == 'PKG_CHECK_EXISTS':
                res = pkgce_re.search(value)
                if res:
                    res = dep_re.findall(res.group(1))
                    if res:
                        pcdeps.extend([x[0] for x in res])
                inherits.append('pkgconfig')
            elif keyword in ('AM_GNU_GETTEXT', 'AM_GLIB_GNU_GETTEXT', 'GETTEXT_PACKAGE'):
                inherits.append('gettext')
            elif keyword in ('AC_PROG_INTLTOOL', 'IT_PROG_INTLTOOL'):
                deps.append('intltool-native')
            elif keyword == 'AM_PATH_GLIB_2_0':
                deps.append('glib-2.0')
            elif keyword in ('AC_CHECK_PROG', 'AC_PATH_PROG', 'AX_WITH_PROG'):
                res = progs_re.search(value)
                if res:
                    for prog in shlex.split(res.group(1)):
                        prog = prog.split()[0]
                        for handler in handlers:
                            if handler.process_prog(srctree, keyword, value, prog, deps, outlines, inherits, values):
                                return
                        progclass = progclassmap.get(prog, None)
                        if progclass:
                            inherits.append(progclass)
                        else:
                            progdep = RecipeHandler.recipebinmap.get(prog, None)
                            if not progdep:
                                progdep = progmap.get(prog, None)
                            if progdep:
                                deps.append(progdep)
                            elif progdep is None:
                                if not prog.startswith('$'):
                                    unmapped.append(prog)
            elif keyword == 'AC_CHECK_LIB':
                res = lib_re.search(value)
                if res:
                    lib = res.group(1)
                    if not lib.startswith('$'):
                        libdeps.append(lib)
            elif keyword == 'AX_CHECK_LIBRARY':
                res = libx_re.search(value)
                if res:
                    lib = res.group(2)
                    if not lib.startswith('$'):
                        header = res.group(1)
                        libdeps.append((lib, header))
            elif keyword == 'AC_PATH_X':
                deps.append('libx11')
            elif keyword in ('AX_BOOST', 'BOOST_REQUIRE'):
                deps.append('boost')
            elif keyword in ('AC_PROG_LEX', 'AM_PROG_LEX', 'AX_PROG_FLEX'):
                deps.append('flex-native')
            elif keyword in ('AC_PROG_YACC', 'AX_PROG_BISON'):
                deps.append('bison-native')
            elif keyword == 'AX_CHECK_ZLIB':
                deps.append('zlib')
            elif keyword in ('AX_CHECK_OPENSSL', 'AX_LIB_CRYPTO'):
                deps.append('openssl')
            elif keyword == 'AX_LIB_CURL':
                deps.append('curl')
            elif keyword == 'AX_LIB_BEECRYPT':
                deps.append('beecrypt')
            elif keyword == 'AX_LIB_EXPAT':
                deps.append('expat')
            elif keyword == 'AX_LIB_GCRYPT':
                deps.append('libgcrypt')
            elif keyword == 'AX_LIB_NETTLE':
                deps.append('nettle')
            elif keyword == 'AX_LIB_READLINE':
                deps.append('readline')
            elif keyword == 'AX_LIB_SQLITE3':
                deps.append('sqlite3')
            elif keyword == 'AX_LIB_TAGLIB':
                deps.append('taglib')
            elif keyword in ['AX_PKG_SWIG', 'AC_PROG_SWIG']:
                deps.append('swig-native')
            elif keyword == 'AX_PROG_XSLTPROC':
                deps.append('libxslt-native')
            elif keyword in ['AC_PYTHON_DEVEL', 'AX_PYTHON_DEVEL', 'AM_PATH_PYTHON']:
                pythonclass = 'pythonnative'
                res = version_re.search(value)
                if res:
                    if res.group(1).startswith('3'):
                        pythonclass = 'python3native'
                # Avoid replacing python3native with pythonnative
                if not pythonclass in inherits and not 'python3native' in inherits:
                    if 'pythonnative' in inherits:
                        inherits.remove('pythonnative')
                    inherits.append(pythonclass)
            elif keyword == 'AX_WITH_CURSES':
                deps.append('ncurses')
            elif keyword == 'AX_PATH_BDB':
                deps.append('db')
            elif keyword == 'AX_PATH_LIB_PCRE':
                deps.append('libpcre')
            elif keyword == 'AC_INIT':
                if extravalues is not None:
                    res = ac_init_re.match(value)
                    if res:
                        extravalues['PN'] = process_value(res.group(1))
                        pv = process_value(res.group(2))
                        if validate_pv(pv):
                            extravalues['PV'] = pv
            elif keyword == 'AM_INIT_AUTOMAKE':
                if extravalues is not None:
                    if 'PN' not in extravalues:
                        res = am_init_re.match(value)
                        if res:
                            if res.group(1) != 'AC_PACKAGE_NAME':
                                extravalues['PN'] = process_value(res.group(1))
                            pv = process_value(res.group(2))
                            if validate_pv(pv):
                                extravalues['PV'] = pv
            elif keyword == 'define(':
                res = define_re.match(value)
                if res:
                    key = res.group(2).strip('[]')
                    value = process_value(res.group(3))
                    if value is not None:
                        defines[key] = value

        keywords = ['PKG_CHECK_MODULES',
                    'PKG_CHECK_EXISTS',
                    'AM_GNU_GETTEXT',
                    'AM_GLIB_GNU_GETTEXT',
                    'GETTEXT_PACKAGE',
                    'AC_PROG_INTLTOOL',
                    'IT_PROG_INTLTOOL',
                    'AM_PATH_GLIB_2_0',
                    'AC_CHECK_PROG',
                    'AC_PATH_PROG',
                    'AX_WITH_PROG',
                    'AC_CHECK_LIB',
                    'AX_CHECK_LIBRARY',
                    'AC_PATH_X',
                    'AX_BOOST',
                    'BOOST_REQUIRE',
                    'AC_PROG_LEX',
                    'AM_PROG_LEX',
                    'AX_PROG_FLEX',
                    'AC_PROG_YACC',
                    'AX_PROG_BISON',
                    'AX_CHECK_ZLIB',
                    'AX_CHECK_OPENSSL',
                    'AX_LIB_CRYPTO',
                    'AX_LIB_CURL',
                    'AX_LIB_BEECRYPT',
                    'AX_LIB_EXPAT',
                    'AX_LIB_GCRYPT',
                    'AX_LIB_NETTLE',
                    'AX_LIB_READLINE'
                    'AX_LIB_SQLITE3',
                    'AX_LIB_TAGLIB',
                    'AX_PKG_SWIG',
                    'AC_PROG_SWIG',
                    'AX_PROG_XSLTPROC',
                    'AC_PYTHON_DEVEL',
                    'AX_PYTHON_DEVEL',
                    'AM_PATH_PYTHON',
                    'AX_WITH_CURSES',
                    'AX_PATH_BDB',
                    'AX_PATH_LIB_PCRE',
                    'AC_INIT',
                    'AM_INIT_AUTOMAKE',
                    'define(',
                    ]

        for handler in handlers:
            handler.extend_keywords(keywords)

        for srcfile in srcfiles:
            nesting = 0
            in_keyword = ''
            partial = ''
            with open(srcfile, 'r', errors='surrogateescape') as f:
                for line in f:
                    if in_keyword:
                        partial += ' ' + line.strip()
                        if partial.endswith('\\'):
                            partial = partial[:-1]
                        nesting = nesting + line.count('(') - line.count(')')
                        if nesting == 0:
                            process_macro(in_keyword, partial)
                            partial = ''
                            in_keyword = ''
                    else:
                        for keyword in keywords:
                            if keyword in line:
                                nesting = line.count('(') - line.count(')')
                                if nesting > 0:
                                    partial = line.strip()
                                    if partial.endswith('\\'):
                                        partial = partial[:-1]
                                    in_keyword = keyword
                                else:
                                    process_macro(keyword, line.strip())
                                break

            if in_keyword:
                process_macro(in_keyword, partial)

        if extravalues:
            for k,v in list(extravalues.items()):
                if v:
                    if v.startswith('$') or v.startswith('@') or v.startswith('%'):
                        del extravalues[k]
                    else:
                        extravalues[k] = v.strip('"\'').rstrip('()')

        if unmapped:
            outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmapped))))

        RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)

        for handler in handlers:
            handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values)

        if inherits:
            values['inherit'] = ' '.join(list(set(inherits)))

        return values


class AutotoolsExtensionHandler(object):
    '''Base class for Autotools extension handlers'''
    def process_macro(self, srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values):
        '''
        Handle a macro parsed out of an autotools file. Note that if you want this to be called
        for any macro other than the ones AutotoolsRecipeHandler already looks for, you'll need
        to add it to the keywords list in extend_keywords().
        Return True if you've completely handled the passed in macro, otherwise return False.
        '''
        return False

    def extend_keywords(self, keywords):
        '''Adds keywords to be recognised by the parser (so that you get a call to process_macro)'''
        return

    def process_prog(self, srctree, keyword, value, prog, deps, outlines, inherits, values):
        '''
        Handle an AC_PATH_PROG, AC_CHECK_PROG etc. line
        Return True if you've completely handled the passed in macro, otherwise return False.
        '''
        return False

    def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values):
        '''
        Apply any desired post-processing on the output
        '''
        return


class MakefileRecipeHandler(RecipeHandler):
    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
        if 'buildsystem' in handled:
            return False

        makefile = RecipeHandler.checkfiles(srctree, ['Makefile', 'makefile', 'GNUmakefile'])
        if makefile:
            lines_after.append('# NOTE: this is a Makefile-only piece of software, so we cannot generate much of the')
            lines_after.append('# recipe automatically - you will need to examine the Makefile yourself and ensure')
            lines_after.append('# that the appropriate arguments are passed in.')
            lines_after.append('')

            scanfile = os.path.join(srctree, 'configure.scan')
            skipscan = False
            try:
                stdout, stderr = bb.process.run('autoscan', cwd=srctree, shell=True)
            except bb.process.ExecutionError as e:
                skipscan = True
            if scanfile and os.path.exists(scanfile):
                values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, acfile=scanfile)
                classes.extend(values.pop('inherit', '').split())
                for var, value in values.items():
                    if var == 'DEPENDS':
                        lines_before.append('# NOTE: some of these dependencies may be optional, check the Makefile and/or upstream documentation')
                    lines_before.append('%s = "%s"' % (var, value))
                lines_before.append('')
                for f in ['configure.scan', 'autoscan.log']:
                    fp = os.path.join(srctree, f)
                    if os.path.exists(fp):
                        os.remove(fp)

            self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here'])

            func = []
            func.append('# You will almost certainly need to add additional arguments here')
            func.append('oe_runmake')
            self.genfunction(lines_after, 'do_compile', func)

            installtarget = True
            try:
                stdout, stderr = bb.process.run('make -n install', cwd=srctree, shell=True)
            except bb.process.ExecutionError as e:
                if e.exitcode != 1:
                    installtarget = False
            func = []
            if installtarget:
                func.append('# This is a guess; additional arguments may be required')
                makeargs = ''
                with open(makefile[0], 'r', errors='surrogateescape') as f:
                    for i in range(1, 100):
                        if 'DESTDIR' in f.readline():
                            makeargs += " 'DESTDIR=${D}'"
                            break
                func.append('oe_runmake install%s' % makeargs)
            else:
                func.append('# NOTE: unable to determine what to put here - there is a Makefile but no')
                func.append('# target named "install", so you will need to define this yourself')
            self.genfunction(lines_after, 'do_install', func)

            handled.append('buildsystem')
        else:
            lines_after.append('# NOTE: no Makefile found, unable to determine what needs to be done')
            lines_after.append('')
            self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here'])
            self.genfunction(lines_after, 'do_compile', ['# Specify compilation commands here'])
            self.genfunction(lines_after, 'do_install', ['# Specify install commands here'])


class VersionFileRecipeHandler(RecipeHandler):
    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
        if 'PV' not in extravalues:
            # Look for a VERSION or version file containing a single line consisting
            # only of a version number
            filelist = RecipeHandler.checkfiles(srctree, ['VERSION', 'version'])
            version = None
            for fileitem in filelist:
                linecount = 0
                with open(fileitem, 'r', errors='surrogateescape') as f:
                    for line in f:
                        line = line.rstrip().strip('"\'')
                        linecount += 1
                        if line:
                            if linecount > 1:
                                version = None
                                break
                            else:
                                if validate_pv(line):
                                    version = line
                if version:
                    extravalues['PV'] = version
                    break


class SpecFileRecipeHandler(RecipeHandler):
    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
        if 'PV' in extravalues and 'PN' in extravalues:
            return
        filelist = RecipeHandler.checkfiles(srctree, ['*.spec'], recursive=True)
        valuemap = {'Name': 'PN',
                    'Version': 'PV',
                    'Summary': 'SUMMARY',
                    'Url': 'HOMEPAGE',
                    'License': 'LICENSE'}
        foundvalues = {}
        for fileitem in filelist:
            linecount = 0
            with open(fileitem, 'r', errors='surrogateescape') as f:
                for line in f:
                    for value, varname in valuemap.items():
                        if line.startswith(value + ':') and not varname in foundvalues:
                            foundvalues[varname] = line.split(':', 1)[1].strip()
                            break
                    if len(foundvalues) == len(valuemap):
                        break
        if 'PV' in foundvalues:
            if not validate_pv(foundvalues['PV']):
                del foundvalues['PV']
        license = foundvalues.pop('LICENSE', None)
        if license:
            liccomment = '# NOTE: spec file indicates the license may be "%s"' % license
            for i, line in enumerate(lines_before):
                if line.startswith('LICENSE ='):
                    lines_before.insert(i, liccomment)
                    break
            else:
                lines_before.append(liccomment)
        extravalues.update(foundvalues)

def register_recipe_handlers(handlers):
    # Set priorities with some gaps so that other plugins can insert
    # their own handlers (so avoid changing these numbers)
    handlers.append((CmakeRecipeHandler(), 50))
    handlers.append((AutotoolsRecipeHandler(), 40))
    handlers.append((SconsRecipeHandler(), 30))
    handlers.append((QmakeRecipeHandler(), 20))
    handlers.append((MakefileRecipeHandler(), 10))
    handlers.append((VersionFileRecipeHandler(), -1))
    handlers.append((SpecFileRecipeHandler(), -1))