mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 03:49:07 +02:00

Allow plugins to set any variable value through the extravalues dict, and use this to support extracting SUMMARY and HOMEPAGE values from spec files included with the source; additionally translate "License:" to a comment next to the LICENSE field (we have our own logic for setting LICENSE, but it will often be useful to see what the spec file says if one is present). Also use the same mechanism for setting the same variables for node.js modules; this was already supported but wasn't inserting the settings in the appropriate place in the file which this will now do. (From OE-Core rev: 91fc35ff5e89aa6d4c4ad945e45406fb4f71018e) Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
873 lines
38 KiB
Python
873 lines
38 KiB
Python
# 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.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('')
|
|
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.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, 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.iteritems():
|
|
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') 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') 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*([^,]+)\)')
|
|
|
|
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, ['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
|
|
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 == '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:
|
|
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
|
|
|
|
if in_keyword:
|
|
process_macro(in_keyword, partial)
|
|
|
|
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: 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'])
|
|
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.iteritems():
|
|
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') 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') 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') as f:
|
|
for line in f:
|
|
for value, varname in valuemap.iteritems():
|
|
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))
|