From 5638ca2b9483806d991d93071b259769a5f7ec48 Mon Sep 17 00:00:00 2001 From: Paul Eggleton Date: Fri, 19 Dec 2014 11:41:53 +0000 Subject: scripts/recipetool: Add a recipe auto-creation script Add a more maintainable and flexible script for creating at least the skeleton of a recipe based on an examination of the source tree. Commands can be added and the creation process can be extended through plugins. [YOCTO #6406] (From OE-Core rev: fa07ada1cd0750f9aa6bcc31f8236205edf6b4ed) Signed-off-by: Paul Eggleton Signed-off-by: Richard Purdie --- scripts/lib/recipetool/create_buildsys.py | 319 ++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 scripts/lib/recipetool/create_buildsys.py (limited to 'scripts/lib/recipetool/create_buildsys.py') diff --git a/scripts/lib/recipetool/create_buildsys.py b/scripts/lib/recipetool/create_buildsys.py new file mode 100644 index 0000000000..6c9e0efa2a --- /dev/null +++ b/scripts/lib/recipetool/create_buildsys.py @@ -0,0 +1,319 @@ +# Recipe creation tool - create command build system handlers +# +# Copyright (C) 2014 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 +from recipetool.create import RecipeHandler, read_pkgconfig_provides + +logger = logging.getLogger('recipetool') + +tinfoil = None + +def tinfoil_init(instance): + global tinfoil + tinfoil = instance + +class CmakeRecipeHandler(RecipeHandler): + def process(self, srctree, classes, lines_before, lines_after, handled): + if 'buildsystem' in handled: + return False + + if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']): + classes.append('cmake') + 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 + +class SconsRecipeHandler(RecipeHandler): + def process(self, srctree, classes, lines_before, lines_after, handled): + 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): + 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): + 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) + 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: + 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, acfile=None): + import shlex + import oe.package + + values = {} + inherits = [] + + # FIXME this mapping is very thin + progmap = {'flex': 'flex-native', + 'bison': 'bison-native', + 'm4': 'm4-native'} + 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]*\]?, \[?([^,\]]*)\]?[),].*') + 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 + + # 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']) + pcdeps = [] + 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) + 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) + 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: + 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 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)') + + 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) + + deps = set(deps).difference(set(ignoredeps)) + + 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)') + + if deps: + values['DEPENDS'] = ' '.join(deps) + + if inherits: + values['inherit'] = ' '.join(list(set(inherits))) + + return values + + +class MakefileRecipeHandler(RecipeHandler): + def process(self, srctree, classes, lines_before, lines_after, handled): + 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 -qn 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']) + + +def plugin_init(pluginlist): + pass + +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()) -- cgit v1.2.3-54-g00ecf