diff options
Diffstat (limited to 'yocto-poky/scripts/lib/recipetool/create_buildsys.py')
-rw-r--r-- | yocto-poky/scripts/lib/recipetool/create_buildsys.py | 777 |
1 files changed, 660 insertions, 117 deletions
diff --git a/yocto-poky/scripts/lib/recipetool/create_buildsys.py b/yocto-poky/scripts/lib/recipetool/create_buildsys.py index 931ef3b33..f84ec3dc6 100644 --- a/yocto-poky/scripts/lib/recipetool/create_buildsys.py +++ b/yocto-poky/scripts/lib/recipetool/create_buildsys.py @@ -1,6 +1,6 @@ # Recipe creation tool - create command build system handlers # -# Copyright (C) 2014 Intel Corporation +# 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 @@ -17,23 +17,35 @@ import re import logging -from recipetool.create import RecipeHandler, read_pkgconfig_provides +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): + 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.iteritems(): + 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('') @@ -41,8 +53,266 @@ class CmakeRecipeHandler(RecipeHandler): 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.iteritems(): + 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') 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): + def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): if 'buildsystem' in handled: return False @@ -55,8 +325,9 @@ class SconsRecipeHandler(RecipeHandler): return True return False + class QmakeRecipeHandler(RecipeHandler): - def process(self, srctree, classes, lines_before, lines_after, handled): + def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): if 'buildsystem' in handled: return False @@ -66,15 +337,16 @@ class QmakeRecipeHandler(RecipeHandler): return True return False + class AutotoolsRecipeHandler(RecipeHandler): - def process(self, srctree, classes, lines_before, lines_after, handled): + 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) + values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, extravalues) classes.extend(values.pop('inherit', '').split()) for var, value in values.iteritems(): lines_before.append('%s = "%s"' % (var, value)) @@ -88,7 +360,24 @@ class AutotoolsRecipeHandler(RecipeHandler): 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') 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') @@ -102,136 +391,311 @@ class AutotoolsRecipeHandler(RecipeHandler): return False @staticmethod - def extract_autotools_deps(outlines, srctree, acfile=None): + def extract_autotools_deps(outlines, srctree, extravalues=None, acfile=None): import shlex - import oe.package + + # 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 = [] - # FIXME this mapping is very thin + # 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'} + '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'} - - ignoredeps = ['gcc-runtime', 'glibc', 'uclibc'] - - pkg_re = re.compile('PKG_CHECK_MODULES\(\[?[a-zA-Z0-9]*\]?, \[?([^,\]]*)[),].*') - lib_re = re.compile('AC_CHECK_LIB\(\[?([a-zA-Z0-9]*)\]?, .*') - progs_re = re.compile('_PROGS?\(\[?[a-zA-Z0-9]*\]?, \[?([^,\]]*)\]?[),].*') + '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('([^ ><=]+)( [<>=]+ [^ ><=]+)?') - - # Build up lib library->package mapping - shlib_providers = oe.package.read_shlib_providers(tinfoil.config_data) - libdir = tinfoil.config_data.getVar('libdir', True) - base_libdir = tinfoil.config_data.getVar('base_libdir', True) - libpaths = list(set([base_libdir, libdir])) - libname_re = re.compile('^lib(.+)\.so.*$') - pkglibmap = {} - for lib, item in shlib_providers.iteritems(): - for path, pkg in item.iteritems(): - if path in libpaths: - res = libname_re.match(lib) - if res: - libname = res.group(1) - if not libname in pkglibmap: - pkglibmap[libname] = pkg[0] - else: - logger.debug('unable to extract library name from %s' % lib) - - # Now turn it into a library->recipe mapping - recipelibmap = {} - pkgdata_dir = tinfoil.config_data.getVar('PKGDATA_DIR', True) - for libname, pkg in pkglibmap.iteritems(): - try: - with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f: - for line in f: - if line.startswith('PN:'): - recipelibmap[libname] = line.split(':', 1)[-1].strip() - break - except IOError as ioe: - if ioe.errno == 2: - logger.warn('unable to find a pkgdata file for package %s' % pkg) - else: - raise + 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*([^,]+)\)') + + defines = {} + def subst_defines(value): + newvalue = value + for define, defval in defines.iteritems(): + 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, ['configure.ac', 'configure.in']) + srcfiles = RecipeHandler.checkfiles(srctree, ['acinclude.m4', 'configure.ac', 'configure.in']) + pcdeps = [] + libdeps = [] deps = [] unmapped = [] - unmappedlibs = [] - with open(srcfiles[0], 'r') as f: - for line in f: - if 'PKG_CHECK_MODULES' in line: - res = pkg_re.search(line) + + 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 + if keyword == 'PKG_CHECK_MODULES': + res = pkg_re.search(value) + if res: + res = dep_re.findall(res.group(1)) if res: - res = dep_re.findall(res.group(1)) - if res: - pcdeps.extend([x[0] for x in res]) - inherits.append('pkgconfig') - if line.lstrip().startswith('AM_GNU_GETTEXT'): - inherits.append('gettext') - elif 'AC_CHECK_PROG' in line or 'AC_PATH_PROG' in line: - res = progs_re.search(line) + 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: - for prog in shlex.split(res.group(1)): - prog = prog.split()[0] - progclass = progclassmap.get(prog, None) - if progclass: - inherits.append(progclass) - else: + 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) - else: - if not prog.startswith('$'): - unmapped.append(prog) - elif 'AC_CHECK_LIB' in line: - res = lib_re.search(line) + 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 == 'AX_PKG_SWIG': + deps.append('swig') + elif keyword == 'AX_PROG_XSLTPROC': + deps.append('libxslt-native') + 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: - lib = res.group(1) - libdep = recipelibmap.get(lib, None) - if libdep: - deps.append(libdep) - else: - if libdep is None: - if not lib.startswith('$'): - unmappedlibs.append(lib) - elif 'AC_PATH_X' in line: - deps.append('libx11') - - if unmapped: - outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(unmapped)) - - if unmappedlibs: - outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(unmappedlibs)) - outlines.append('# (this is based on recipes that have previously been built and packaged)') + 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', + 'AX_PROG_XSLTPROC', + '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') 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 - recipemap = read_pkgconfig_provides(tinfoil.config_data) - unmapped = [] - for pcdep in pcdeps: - recipe = recipemap.get(pcdep, None) - if recipe: - deps.append(recipe) - else: - if not pcdep.startswith('$'): - unmapped.append(pcdep) + if in_keyword: + process_macro(in_keyword, partial) - deps = set(deps).difference(set(ignoredeps)) + if extravalues: + for k,v in 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: unable to map the following pkg-config dependencies: %s' % ' '.join(unmapped)) - outlines.append('# (this is based on recipes that have previously been built and packaged)') + outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmapped)))) - if deps: - values['DEPENDS'] = ' '.join(deps) + 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))) @@ -239,8 +703,37 @@ class AutotoolsRecipeHandler(RecipeHandler): 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): + def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): if 'buildsystem' in handled: return False @@ -307,10 +800,60 @@ class MakefileRecipeHandler(RecipeHandler): 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') 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) + pn = None + pv = None + for fileitem in filelist: + linecount = 0 + with open(fileitem, 'r') as f: + for line in f: + if line.startswith('Name:') and not pn: + pn = line.split(':')[1].strip() + if line.startswith('Version:') and not pv: + pv = line.split(':')[1].strip() + if pv or pn: + if pv and not 'PV' in extravalues and validate_pv(pv): + extravalues['PV'] = pv + if pn and not 'PN' in extravalues: + extravalues['PN'] = pn + break + def register_recipe_handlers(handlers): - # These are in a specific order so that the right one is detected first - handlers.append(CmakeRecipeHandler()) - handlers.append(AutotoolsRecipeHandler()) - handlers.append(SconsRecipeHandler()) - handlers.append(QmakeRecipeHandler()) - handlers.append(MakefileRecipeHandler()) + # 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)) |