| # Recipe creation tool - create command build system handlers |
| # |
| # Copyright (C) 2014-2016 Intel Corporation |
| # |
| # SPDX-License-Identifier: GPL-2.0-only |
| # |
| |
| 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('python3native') |
| elif pkg == 'PythonLibs': |
| inherits.append('python3-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': 'python3native', |
| '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 = 'python3native' |
| 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 |
| # Drop values containing unexpanded RPM macros |
| for k in list(foundvalues.keys()): |
| if '%' in foundvalues[k]: |
| del foundvalues[k] |
| 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)) |