summaryrefslogtreecommitdiff
path: root/yocto-poky/scripts/lib/recipetool
diff options
context:
space:
mode:
Diffstat (limited to 'yocto-poky/scripts/lib/recipetool')
-rw-r--r--yocto-poky/scripts/lib/recipetool/append.py4
-rw-r--r--yocto-poky/scripts/lib/recipetool/create.py583
-rw-r--r--yocto-poky/scripts/lib/recipetool/create_buildsys.py777
-rw-r--r--yocto-poky/scripts/lib/recipetool/create_buildsys_python.py9
-rw-r--r--yocto-poky/scripts/lib/recipetool/create_kernel.py99
-rw-r--r--yocto-poky/scripts/lib/recipetool/create_kmod.py152
-rw-r--r--yocto-poky/scripts/lib/recipetool/create_npm.py156
-rw-r--r--yocto-poky/scripts/lib/recipetool/newappend.py17
-rw-r--r--yocto-poky/scripts/lib/recipetool/setvar.py75
9 files changed, 1699 insertions, 173 deletions
diff --git a/yocto-poky/scripts/lib/recipetool/append.py b/yocto-poky/scripts/lib/recipetool/append.py
index 7fe411520..558fd25ac 100644
--- a/yocto-poky/scripts/lib/recipetool/append.py
+++ b/yocto-poky/scripts/lib/recipetool/append.py
@@ -343,6 +343,8 @@ def appendsrc(args, files, rd, extralines=None):
simplified = {}
src_uri = rd.getVar('SRC_URI', True).split()
for uri in src_uri:
+ if uri.endswith(';'):
+ uri = uri[:-1]
simple_uri = bb.fetch.URI(uri)
simple_uri.params = {}
simplified[str(simple_uri)] = uri
@@ -433,7 +435,7 @@ def target_path(targetpath):
return targetpath
-def register_command(subparsers):
+def register_commands(subparsers):
common = argparse.ArgumentParser(add_help=False)
common.add_argument('-m', '--machine', help='Make bbappend changes specific to a machine only', metavar='MACHINE')
common.add_argument('-w', '--wildcard-version', help='Use wildcard to make the bbappend apply to any recipe version', action='store_true')
diff --git a/yocto-poky/scripts/lib/recipetool/create.py b/yocto-poky/scripts/lib/recipetool/create.py
index 8305e4364..bb9fb9b04 100644
--- a/yocto-poky/scripts/lib/recipetool/create.py
+++ b/yocto-poky/scripts/lib/recipetool/create.py
@@ -1,6 +1,6 @@
# Recipe creation tool - create command plugin
#
-# Copyright (C) 2014-2015 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
@@ -21,9 +21,11 @@ import argparse
import glob
import fnmatch
import re
+import json
import logging
import scriptutils
import urlparse
+import hashlib
logger = logging.getLogger('recipetool')
@@ -39,14 +41,187 @@ def tinfoil_init(instance):
global tinfoil
tinfoil = instance
-class RecipeHandler():
+class RecipeHandler(object):
+ recipelibmap = {}
+ recipeheadermap = {}
+ recipecmakefilemap = {}
+ recipebinmap = {}
+
+ @staticmethod
+ def load_libmap(d):
+ '''Load library->recipe mapping'''
+ import oe.package
+
+ if RecipeHandler.recipelibmap:
+ return
+ # First build up library->package mapping
+ shlib_providers = oe.package.read_shlib_providers(d)
+ libdir = d.getVar('libdir', True)
+ base_libdir = d.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
+ pkgdata_dir = d.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:'):
+ RecipeHandler.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
+
+ # Some overrides - these should be mapped to the virtual
+ RecipeHandler.recipelibmap['GL'] = 'virtual/libgl'
+ RecipeHandler.recipelibmap['EGL'] = 'virtual/egl'
+ RecipeHandler.recipelibmap['GLESv2'] = 'virtual/libgles2'
+
+ @staticmethod
+ def load_devel_filemap(d):
+ '''Build up development file->recipe mapping'''
+ if RecipeHandler.recipeheadermap:
+ return
+ pkgdata_dir = d.getVar('PKGDATA_DIR', True)
+ includedir = d.getVar('includedir', True)
+ cmakedir = os.path.join(d.getVar('libdir', True), 'cmake')
+ for pkg in glob.glob(os.path.join(pkgdata_dir, 'runtime', '*-dev')):
+ with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
+ pn = None
+ headers = []
+ cmakefiles = []
+ for line in f:
+ if line.startswith('PN:'):
+ pn = line.split(':', 1)[-1].strip()
+ elif line.startswith('FILES_INFO:'):
+ val = line.split(':', 1)[1].strip()
+ dictval = json.loads(val)
+ for fullpth in sorted(dictval):
+ if fullpth.startswith(includedir) and fullpth.endswith('.h'):
+ headers.append(os.path.relpath(fullpth, includedir))
+ elif fullpth.startswith(cmakedir) and fullpth.endswith('.cmake'):
+ cmakefiles.append(os.path.relpath(fullpth, cmakedir))
+ if pn and headers:
+ for header in headers:
+ RecipeHandler.recipeheadermap[header] = pn
+ if pn and cmakefiles:
+ for fn in cmakefiles:
+ RecipeHandler.recipecmakefilemap[fn] = pn
+
@staticmethod
- def checkfiles(path, speclist):
+ def load_binmap(d):
+ '''Build up native binary->recipe mapping'''
+ if RecipeHandler.recipebinmap:
+ return
+ sstate_manifests = d.getVar('SSTATE_MANIFESTS', True)
+ staging_bindir_native = d.getVar('STAGING_BINDIR_NATIVE', True)
+ build_arch = d.getVar('BUILD_ARCH', True)
+ fileprefix = 'manifest-%s-' % build_arch
+ for fn in glob.glob(os.path.join(sstate_manifests, '%s*-native.populate_sysroot' % fileprefix)):
+ with open(fn, 'r') as f:
+ pn = os.path.basename(fn).rsplit('.', 1)[0][len(fileprefix):]
+ for line in f:
+ if line.startswith(staging_bindir_native):
+ prog = os.path.basename(line.rstrip())
+ RecipeHandler.recipebinmap[prog] = pn
+
+ @staticmethod
+ def checkfiles(path, speclist, recursive=False):
results = []
- for spec in speclist:
- results.extend(glob.glob(os.path.join(path, spec)))
+ if recursive:
+ for root, _, files in os.walk(path):
+ for fn in files:
+ for spec in speclist:
+ if fnmatch.fnmatch(fn, spec):
+ results.append(os.path.join(root, fn))
+ else:
+ for spec in speclist:
+ results.extend(glob.glob(os.path.join(path, spec)))
return results
+ @staticmethod
+ def handle_depends(libdeps, pcdeps, deps, outlines, values, d):
+ if pcdeps:
+ recipemap = read_pkgconfig_provides(d)
+ if libdeps:
+ RecipeHandler.load_libmap(d)
+
+ ignorelibs = ['socket']
+ ignoredeps = ['gcc-runtime', 'glibc', 'uclibc', 'musl', 'tar-native', 'binutils-native', 'coreutils-native']
+
+ unmappedpc = []
+ pcdeps = list(set(pcdeps))
+ for pcdep in pcdeps:
+ if isinstance(pcdep, basestring):
+ recipe = recipemap.get(pcdep, None)
+ if recipe:
+ deps.append(recipe)
+ else:
+ if not pcdep.startswith('$'):
+ unmappedpc.append(pcdep)
+ else:
+ for item in pcdep:
+ recipe = recipemap.get(pcdep, None)
+ if recipe:
+ deps.append(recipe)
+ break
+ else:
+ unmappedpc.append('(%s)' % ' or '.join(pcdep))
+
+ unmappedlibs = []
+ for libdep in libdeps:
+ if isinstance(libdep, tuple):
+ lib, header = libdep
+ else:
+ lib = libdep
+ header = None
+
+ if lib in ignorelibs:
+ logger.debug('Ignoring library dependency %s' % lib)
+ continue
+
+ recipe = RecipeHandler.recipelibmap.get(lib, None)
+ if recipe:
+ deps.append(recipe)
+ elif recipe is None:
+ if header:
+ RecipeHandler.load_devel_filemap(d)
+ recipe = RecipeHandler.recipeheadermap.get(header, None)
+ if recipe:
+ deps.append(recipe)
+ elif recipe is None:
+ unmappedlibs.append(lib)
+ else:
+ unmappedlibs.append(lib)
+
+ deps = set(deps).difference(set(ignoredeps))
+
+ if unmappedpc:
+ outlines.append('# NOTE: unable to map the following pkg-config dependencies: %s' % ' '.join(unmappedpc))
+ outlines.append('# (this is based on recipes that have previously been built and packaged)')
+
+ if unmappedlibs:
+ outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmappedlibs))))
+ outlines.append('# (this is based on recipes that have previously been built and packaged)')
+
+ if deps:
+ values['DEPENDS'] = ' '.join(deps)
+
def genfunction(self, outlines, funcname, content, python=False, forcespace=False):
if python:
prefix = 'python '
@@ -70,10 +245,64 @@ class RecipeHandler():
outlines.append('}')
outlines.append('')
- def process(self, srctree, classes, lines_before, lines_after, handled):
+ def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
return False
+def validate_pv(pv):
+ if not pv or '_version' in pv.lower() or pv[0] not in '0123456789':
+ return False
+ return True
+
+def determine_from_filename(srcfile):
+ """Determine name and version from a filename"""
+ part = ''
+ if '.tar.' in srcfile:
+ namepart = srcfile.split('.tar.')[0].lower()
+ else:
+ namepart = os.path.splitext(srcfile)[0].lower()
+ splitval = namepart.rsplit('_', 1)
+ if len(splitval) == 1:
+ splitval = namepart.rsplit('-', 1)
+ pn = splitval[0].replace('_', '-')
+ if len(splitval) > 1:
+ if splitval[1][0] in '0123456789':
+ pv = splitval[1]
+ else:
+ pn = '-'.join(splitval).replace('_', '-')
+ pv = None
+ else:
+ pv = None
+ return (pn, pv)
+
+def determine_from_url(srcuri):
+ """Determine name and version from a URL"""
+ pn = None
+ pv = None
+ parseres = urlparse.urlparse(srcuri.lower().split(';', 1)[0])
+ if parseres.path:
+ if 'github.com' in parseres.netloc:
+ res = re.search(r'.*/(.*?)/archive/(.*)-final\.(tar|zip)', parseres.path)
+ if res:
+ pn = res.group(1).strip().replace('_', '-')
+ pv = res.group(2).strip().replace('_', '.')
+ else:
+ res = re.search(r'.*/(.*?)/archive/v?(.*)\.(tar|zip)', parseres.path)
+ if res:
+ pn = res.group(1).strip().replace('_', '-')
+ pv = res.group(2).strip().replace('_', '.')
+ elif 'bitbucket.org' in parseres.netloc:
+ res = re.search(r'.*/(.*?)/get/[a-zA-Z_-]*([0-9][0-9a-zA-Z_.]*)\.(tar|zip)', parseres.path)
+ if res:
+ pn = res.group(1).strip().replace('_', '-')
+ pv = res.group(2).strip().replace('_', '.')
+
+ if not pn and not pv:
+ srcfile = os.path.basename(parseres.path.rstrip('/'))
+ pn, pv = determine_from_filename(srcfile)
+
+ logger.debug('Determined from source URL: name = "%s", version = "%s"' % (pn, pv))
+ return (pn, pv)
def supports_srcrev(uri):
localdata = bb.data.createCopy(tinfoil.config_data)
@@ -88,6 +317,16 @@ def supports_srcrev(uri):
return True
return False
+def reformat_git_uri(uri):
+ '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
+ checkuri = uri.split(';', 1)[0]
+ if checkuri.endswith('.git') or '/git/' in checkuri:
+ res = re.match('(https?)://([^;]+(\.git)?)(;.*)?$', uri)
+ if res:
+ # Need to switch the URI around so that the git fetcher is used
+ return 'git://%s;protocol=%s%s' % (res.group(2), res.group(1), res.group(4) or '')
+ return uri
+
def create_recipe(args):
import bb.process
import tempfile
@@ -103,16 +342,11 @@ def create_recipe(args):
srcrev = '${AUTOREV}'
if '://' in args.source:
# Fetch a URL
- fetchuri = urlparse.urldefrag(args.source)[0]
+ fetchuri = reformat_git_uri(urlparse.urldefrag(args.source)[0])
if args.binary:
# Assume the archive contains the directory structure verbatim
# so we need to extract to a subdirectory
fetchuri += ';subdir=%s' % os.path.splitext(os.path.basename(urlparse.urlsplit(fetchuri).path))[0]
- git_re = re.compile('(https?)://([^;]+\.git)(;.*)?')
- res = git_re.match(fetchuri)
- if res:
- # Need to switch the URI around so that the git fetcher is used
- fetchuri = 'git://%s;protocol=%s%s' % (res.group(2), res.group(1), res.group(3) or '')
srcuri = fetchuri
rev_re = re.compile(';rev=([^;]+)')
res = rev_re.search(srcuri)
@@ -121,11 +355,17 @@ def create_recipe(args):
srcuri = rev_re.sub('', srcuri)
tempsrc = tempfile.mkdtemp(prefix='recipetool-')
srctree = tempsrc
+ if fetchuri.startswith('npm://'):
+ # Check if npm is available
+ npm = bb.utils.which(tinfoil.config_data.getVar('PATH', True), 'npm')
+ if not npm:
+ logger.error('npm:// URL requested but npm is not available - you need to either build nodejs-native or install npm using your package manager')
+ sys.exit(1)
logger.info('Fetching %s...' % srcuri)
try:
checksums = scriptutils.fetch_uri(tinfoil.config_data, fetchuri, srctree, srcrev)
- except bb.fetch2.FetchError:
- # Error already printed
+ except bb.fetch2.BBFetchException as e:
+ logger.error(str(e).rstrip())
sys.exit(1)
dirlist = os.listdir(srctree)
if 'git.indirectionsymlink' in dirlist:
@@ -149,10 +389,35 @@ def create_recipe(args):
if not os.path.isdir(args.source):
logger.error('Invalid source directory %s' % args.source)
sys.exit(1)
- srcuri = ''
srctree = args.source
+ srcuri = ''
+ if os.path.exists(os.path.join(srctree, '.git')):
+ # Try to get upstream repo location from origin remote
+ try:
+ stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
+ except bb.process.ExecutionError as e:
+ stdout = None
+ if stdout:
+ for line in stdout.splitlines():
+ splitline = line.split()
+ if len(splitline) > 1:
+ if splitline[0] == 'origin' and '://' in splitline[1]:
+ srcuri = reformat_git_uri(splitline[1])
+ srcsubdir = 'git'
+ break
+
+ if args.src_subdir:
+ srcsubdir = os.path.join(srcsubdir, args.src_subdir)
+ srctree_use = os.path.join(srctree, args.src_subdir)
+ else:
+ srctree_use = srctree
- outfile = args.outfile
+ if args.outfile and os.path.isdir(args.outfile):
+ outfile = None
+ outdir = args.outfile
+ else:
+ outfile = args.outfile
+ outdir = None
if outfile and outfile != '-':
if os.path.exists(outfile):
logger.error('Output file %s already exists' % outfile)
@@ -166,7 +431,7 @@ def create_recipe(args):
lines_before.append('# (Feel free to remove these comments when editing.)')
lines_before.append('#')
- licvalues = guess_license(srctree)
+ licvalues = guess_license(srctree_use)
lic_files_chksum = []
if licvalues:
licenses = []
@@ -195,29 +460,50 @@ def create_recipe(args):
lines_before.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
lines_before.append('')
+ classes = []
+
# FIXME This is kind of a hack, we probably ought to be using bitbake to do this
- # we'd also want a way to automatically set outfile based upon auto-detecting these values from the source if possible
- recipefn = os.path.splitext(os.path.basename(outfile))[0]
- fnsplit = recipefn.split('_')
- if len(fnsplit) > 1:
- pn = fnsplit[0]
- pv = fnsplit[1]
- else:
- pn = recipefn
- pv = None
+ pn = None
+ pv = None
+ if outfile:
+ recipefn = os.path.splitext(os.path.basename(outfile))[0]
+ fnsplit = recipefn.split('_')
+ if len(fnsplit) > 1:
+ pn = fnsplit[0]
+ pv = fnsplit[1]
+ else:
+ pn = recipefn
if args.version:
pv = args.version
+ if args.name:
+ pn = args.name
+ if args.name.endswith('-native'):
+ if args.also_native:
+ logger.error('--also-native cannot be specified for a recipe named *-native (*-native denotes a recipe that is already only for native) - either remove the -native suffix from the name or drop --also-native')
+ sys.exit(1)
+ classes.append('native')
+ elif args.name.startswith('nativesdk-'):
+ if args.also_native:
+ logger.error('--also-native cannot be specified for a recipe named nativesdk-* (nativesdk-* denotes a recipe that is already only for nativesdk)')
+ sys.exit(1)
+ classes.append('nativesdk')
+
if pv and pv not in 'git svn hg'.split():
realpv = pv
else:
realpv = None
- if srcuri:
- if realpv:
- srcuri = srcuri.replace(realpv, '${PV}')
- else:
+ if srcuri and not realpv or not pn:
+ name_pn, name_pv = determine_from_url(srcuri)
+ if name_pn and not pn:
+ pn = name_pn
+ if name_pv and not realpv:
+ realpv = name_pv
+
+
+ if not srcuri:
lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
lines_before.append('SRC_URI = "%s"' % srcuri)
(md5value, sha256value) = checksums
@@ -232,13 +518,7 @@ def create_recipe(args):
lines_before.append('SRCREV = "%s"' % srcrev)
lines_before.append('')
- if srcsubdir and pv:
- if srcsubdir == "%s-%s" % (pn, pv):
- # This would be the default, so we don't need to set S in the recipe
- srcsubdir = ''
if srcsubdir:
- if pv and pv not in 'git svn hg'.split():
- srcsubdir = srcsubdir.replace(pv, '${PV}')
lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir)
lines_before.append('')
@@ -251,25 +531,133 @@ def create_recipe(args):
lines_after.append('')
# Find all plugins that want to register handlers
- handlers = []
+ logger.debug('Loading recipe handlers')
+ raw_handlers = []
for plugin in plugins:
if hasattr(plugin, 'register_recipe_handlers'):
- plugin.register_recipe_handlers(handlers)
+ plugin.register_recipe_handlers(raw_handlers)
+ # Sort handlers by priority
+ handlers = []
+ for i, handler in enumerate(raw_handlers):
+ if isinstance(handler, tuple):
+ handlers.append((handler[0], handler[1], i))
+ else:
+ handlers.append((handler, 0, i))
+ handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
+ for handler, priority, _ in handlers:
+ logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
+ handlers = [item[0] for item in handlers]
# Apply the handlers
- classes = []
handled = []
+ handled.append(('license', licvalues))
if args.binary:
classes.append('bin_package')
handled.append('buildsystem')
+ extravalues = {}
for handler in handlers:
- handler.process(srctree, classes, lines_before, lines_after, handled)
+ handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
+
+ extrafiles = extravalues.pop('extrafiles', {})
+
+ if not realpv:
+ realpv = extravalues.get('PV', None)
+ if realpv:
+ if not validate_pv(realpv):
+ realpv = None
+ else:
+ realpv = realpv.lower().split()[0]
+ if '_' in realpv:
+ realpv = realpv.replace('_', '-')
+ if not pn:
+ pn = extravalues.get('PN', None)
+ if pn:
+ if pn.startswith('GNU '):
+ pn = pn[4:]
+ if ' ' in pn:
+ # Probably a descriptive identifier rather than a proper name
+ pn = None
+ else:
+ pn = pn.lower()
+ if '_' in pn:
+ pn = pn.replace('_', '-')
+
+ if not outfile:
+ if not pn:
+ logger.error('Unable to determine short program name from source tree - please specify name with -N/--name or output file name with -o/--outfile')
+ # devtool looks for this specific exit code, so don't change it
+ sys.exit(15)
+ else:
+ if srcuri and srcuri.startswith(('git://', 'hg://', 'svn://')):
+ outfile = '%s_%s.bb' % (pn, srcuri.split(':', 1)[0])
+ elif realpv:
+ outfile = '%s_%s.bb' % (pn, realpv)
+ else:
+ outfile = '%s.bb' % pn
+ if outdir:
+ outfile = os.path.join(outdir, outfile)
+ # We need to check this again
+ if os.path.exists(outfile):
+ logger.error('Output file %s already exists' % outfile)
+ sys.exit(1)
+
+ # Move any extra files the plugins created to a directory next to the recipe
+ if extrafiles:
+ if outfile == '-':
+ extraoutdir = pn
+ else:
+ extraoutdir = os.path.join(os.path.dirname(outfile), pn)
+ bb.utils.mkdirhier(extraoutdir)
+ for destfn, extrafile in extrafiles.iteritems():
+ shutil.move(extrafile, os.path.join(extraoutdir, destfn))
+
+ lines = lines_before
+ lines_before = []
+ skipblank = True
+ for line in lines:
+ if skipblank:
+ skipblank = False
+ if not line:
+ continue
+ if line.startswith('S = '):
+ if realpv and pv not in 'git svn hg'.split():
+ line = line.replace(realpv, '${PV}')
+ if pn:
+ line = line.replace(pn, '${BPN}')
+ if line == 'S = "${WORKDIR}/${BPN}-${PV}"':
+ skipblank = True
+ continue
+ elif line.startswith('SRC_URI = '):
+ if realpv:
+ line = line.replace(realpv, '${PV}')
+ elif line.startswith('PV = '):
+ if realpv:
+ line = re.sub('"[^+]*\+', '"%s+' % realpv, line)
+ lines_before.append(line)
+
+ if args.also_native:
+ lines = lines_after
+ lines_after = []
+ bbclassextend = None
+ for line in lines:
+ if line.startswith('BBCLASSEXTEND ='):
+ splitval = line.split('"')
+ if len(splitval) > 1:
+ bbclassextend = splitval[1].split()
+ if not 'native' in bbclassextend:
+ bbclassextend.insert(0, 'native')
+ line = 'BBCLASSEXTEND = "%s"' % ' '.join(bbclassextend)
+ lines_after.append(line)
+ if not bbclassextend:
+ lines_after.append('BBCLASSEXTEND = "native"')
outlines = []
outlines.extend(lines_before)
if classes:
+ if outlines[-1] and not outlines[-1].startswith('#'):
+ outlines.append('')
outlines.append('inherit %s' % ' '.join(classes))
outlines.append('')
outlines.extend(lines_after)
@@ -339,19 +727,92 @@ def get_license_md5sums(d, static_only=False):
md5sums['5f30f0716dfdd0d91eb439ebec522ec2'] = 'LGPLv2'
md5sums['55ca817ccb7d5b5b66355690e9abc605'] = 'LGPLv2'
md5sums['252890d9eee26aab7b432e8b8a616475'] = 'LGPLv2'
+ md5sums['3214f080875748938ba060314b4f727d'] = 'LGPLv2'
+ md5sums['db979804f025cf55aabec7129cb671ed'] = 'LGPLv2'
md5sums['d32239bcb673463ab874e80d47fae504'] = 'GPLv3'
md5sums['f27defe1e96c2e1ecd4e0c9be8967949'] = 'GPLv3'
md5sums['6a6a8e020838b23406c81b19c1d46df6'] = 'LGPLv3'
md5sums['3b83ef96387f14655fc854ddc3c6bd57'] = 'Apache-2.0'
md5sums['385c55653886acac3821999a3ccd17b3'] = 'Artistic-1.0 | GPL-2.0' # some perl modules
+ md5sums['54c7042be62e169199200bc6477f04d1'] = 'BSD-3-Clause'
return md5sums
+def crunch_license(licfile):
+ '''
+ Remove non-material text from a license file and then check
+ its md5sum against a known list. This works well for licenses
+ which contain a copyright statement, but is also a useful way
+ to handle people's insistence upon reformatting the license text
+ slightly (with no material difference to the text of the
+ license).
+ '''
+
+ import oe.utils
+
+ # Note: these are carefully constructed!
+ license_title_re = re.compile('^\(?(#+ *)?(The )?.{1,10} [Ll]icen[sc]e( \(.{1,10}\))?\)?:?$')
+ license_statement_re = re.compile('^This (project|software) is( free software)? released under the .{1,10} [Ll]icen[sc]e:?$')
+ copyright_re = re.compile('^(#+)? *Copyright .*$')
+
+ crunched_md5sums = {}
+ # The following two were gleaned from the "forever" npm package
+ crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
+ crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
+ # https://github.com/vasi/pixz/blob/master/LICENSE
+ crunched_md5sums['2f03392b40bbe663597b5bd3cc5ebdb9'] = 'BSD-2-Clause'
+ # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
+ crunched_md5sums['e72e5dfef0b1a4ca8a3d26a60587db66'] = 'BSD-2-Clause'
+ # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
+ crunched_md5sums['8be76ac6d191671f347ee4916baa637e'] = 'GPLv2'
+ # https://github.com/datto/dattobd/blob/master/COPYING
+ # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT
+ crunched_md5sums['1d65c5ad4bf6489f85f4812bf08ae73d'] = 'GPLv2'
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+ # http://git.neil.brown.name/?p=mdadm.git;a=blob;f=COPYING;h=d159169d1050894d3ea3b98e1c965c4058208fe1;hb=HEAD
+ crunched_md5sums['fb530f66a7a89ce920f0e912b5b66d4b'] = 'GPLv2'
+ # https://github.com/gkos/nrf24/blob/master/COPYING
+ crunched_md5sums['7b6aaa4daeafdfa6ed5443fd2684581b'] = 'GPLv2'
+ # https://github.com/josch09/resetusb/blob/master/COPYING
+ crunched_md5sums['8b8ac1d631a4d220342e83bcf1a1fbc3'] = 'GPLv3'
+ # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
+ crunched_md5sums['2ea316ed973ae176e502e2297b574bb3'] = 'LGPLv2.1'
+ # unixODBC-2.3.4 COPYING
+ crunched_md5sums['1daebd9491d1e8426900b4fa5a422814'] = 'LGPLv2.1'
+ # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
+ crunched_md5sums['2ebfb3bb49b9a48a075cc1425e7f4129'] = 'LGPLv3'
+ lictext = []
+ with open(licfile, 'r') as f:
+ for line in f:
+ # Drop opening statements
+ if copyright_re.match(line):
+ continue
+ elif license_title_re.match(line):
+ continue
+ elif license_statement_re.match(line):
+ continue
+ # Squash spaces, and replace smart quotes, double quotes
+ # and backticks with single quotes
+ line = oe.utils.squashspaces(line.strip()).decode("utf-8")
+ line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
+ if line:
+ lictext.append(line)
+
+ m = hashlib.md5()
+ try:
+ m.update(' '.join(lictext))
+ md5val = m.hexdigest()
+ except UnicodeEncodeError:
+ md5val = None
+ lictext = ''
+ license = crunched_md5sums.get(md5val, None)
+ return license, md5val, lictext
+
def guess_license(srctree):
import bb
md5sums = get_license_md5sums(tinfoil.config_data)
licenses = []
- licspecs = ['LICENSE*', 'COPYING*', '*[Ll]icense*', 'LICENCE*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*']
+ licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*']
licfiles = []
for root, dirs, files in os.walk(srctree):
for fn in files:
@@ -362,13 +823,44 @@ def guess_license(srctree):
licfiles.append(fullpath)
for licfile in licfiles:
md5value = bb.utils.md5_file(licfile)
- license = md5sums.get(md5value, 'Unknown')
+ license = md5sums.get(md5value, None)
+ if not license:
+ license, crunched_md5, lictext = crunch_license(licfile)
+ if not license:
+ license = 'Unknown'
licenses.append((license, os.path.relpath(licfile, srctree), md5value))
# FIXME should we grab at least one source file with a license header and add that too?
return licenses
+def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'):
+ """
+ Given a list of (license, path, md5sum) as returned by guess_license(),
+ a dict of package name to path mappings, write out a set of
+ package-specific LICENSE values.
+ """
+ pkglicenses = {pn: []}
+ for license, licpath, _ in licvalues:
+ for pkgname, pkgpath in packages.iteritems():
+ if licpath.startswith(pkgpath + '/'):
+ if pkgname in pkglicenses:
+ pkglicenses[pkgname].append(license)
+ else:
+ pkglicenses[pkgname] = [license]
+ break
+ else:
+ # Accumulate on the main package
+ pkglicenses[pn].append(license)
+ outlicenses = {}
+ for pkgname in packages:
+ license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown']))))
+ if license == 'Unknown' and pkgname in fallback_licenses:
+ license = fallback_licenses[pkgname]
+ outlines.append('LICENSE_%s = "%s"' % (pkgname, license))
+ outlicenses[pkgname] = license.split()
+ return outlicenses
+
def read_pkgconfig_provides(d):
pkgdatadir = d.getVar('PKGDATA_DIR', True)
pkgmap = {}
@@ -454,15 +946,18 @@ def convert_debian(debpath):
return values
-def register_command(subparsers):
+def register_commands(subparsers):
parser_create = subparsers.add_parser('create',
help='Create a new recipe',
description='Creates a new recipe from a source tree')
parser_create.add_argument('source', help='Path or URL to source')
- parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create', required=True)
+ parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create')
parser_create.add_argument('-m', '--machine', help='Make recipe machine-specific as opposed to architecture-specific', action='store_true')
parser_create.add_argument('-x', '--extract-to', metavar='EXTRACTPATH', help='Assuming source is a URL, fetch it and extract it to the directory specified as %(metavar)s')
+ parser_create.add_argument('-N', '--name', help='Name to use within recipe (PN)')
parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
parser_create.add_argument('-b', '--binary', help='Treat the source tree as something that should be installed verbatim (no compilation, same directory structure)', action='store_true')
+ parser_create.add_argument('--also-native', help='Also add native variant (i.e. support building recipe for the build host as well as the target machine)', action='store_true')
+ parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
parser_create.set_defaults(func=create_recipe)
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))
diff --git a/yocto-poky/scripts/lib/recipetool/create_buildsys_python.py b/yocto-poky/scripts/lib/recipetool/create_buildsys_python.py
index e0af2a0f5..c3823307a 100644
--- a/yocto-poky/scripts/lib/recipetool/create_buildsys_python.py
+++ b/yocto-poky/scripts/lib/recipetool/create_buildsys_python.py
@@ -159,7 +159,7 @@ class PythonRecipeHandler(RecipeHandler):
def __init__(self):
pass
- 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
@@ -278,7 +278,10 @@ class PythonRecipeHandler(RecipeHandler):
for k in sorted(bbinfo):
v = bbinfo[k]
mdinfo.append('{} = "{}"'.format(k, v))
- lines_before[src_uri_line-1:src_uri_line-1] = mdinfo
+ if src_uri_line:
+ lines_before[src_uri_line-1:src_uri_line-1] = mdinfo
+ else:
+ lines_before.extend(mdinfo)
mapped_deps, unmapped_deps = self.scan_setup_python_deps(srctree, setup_info, setup_non_literals)
@@ -713,4 +716,4 @@ def has_non_literals(value):
def register_recipe_handlers(handlers):
# We need to make sure this is ahead of the makefile fallback handler
- handlers.insert(0, PythonRecipeHandler())
+ handlers.append((PythonRecipeHandler(), 70))
diff --git a/yocto-poky/scripts/lib/recipetool/create_kernel.py b/yocto-poky/scripts/lib/recipetool/create_kernel.py
new file mode 100644
index 000000000..c6e86bd2b
--- /dev/null
+++ b/yocto-poky/scripts/lib/recipetool/create_kernel.py
@@ -0,0 +1,99 @@
+# Recipe creation tool - kernel support plugin
+#
+# Copyright (C) 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
+from recipetool.create import RecipeHandler, read_pkgconfig_provides, validate_pv
+
+logger = logging.getLogger('recipetool')
+
+tinfoil = None
+
+def tinfoil_init(instance):
+ global tinfoil
+ tinfoil = instance
+
+
+class KernelRecipeHandler(RecipeHandler):
+ def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
+ import bb.process
+ if 'buildsystem' in handled:
+ return False
+
+ for tell in ['arch', 'firmware', 'Kbuild', 'Kconfig']:
+ if not os.path.exists(os.path.join(srctree, tell)):
+ return False
+
+ handled.append('buildsystem')
+ del lines_after[:]
+ del classes[:]
+ template = os.path.join(tinfoil.config_data.getVar('COREBASE', True), 'meta-skeleton', 'recipes-kernel', 'linux', 'linux-yocto-custom.bb')
+ def handle_var(varname, origvalue, op, newlines):
+ if varname in ['SRCREV', 'SRCREV_machine']:
+ while newlines[-1].startswith('#'):
+ del newlines[-1]
+ try:
+ stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree, shell=True)
+ except bb.process.ExecutionError as e:
+ stdout = None
+ if stdout:
+ return stdout.strip(), op, 0, True
+ elif varname == 'LINUX_VERSION':
+ makefile = os.path.join(srctree, 'Makefile')
+ if os.path.exists(makefile):
+ kversion = -1
+ kpatchlevel = -1
+ ksublevel = -1
+ kextraversion = ''
+ with open(makefile, 'r') as f:
+ for i, line in enumerate(f):
+ if i > 10:
+ break
+ if line.startswith('VERSION ='):
+ kversion = int(line.split('=')[1].strip())
+ elif line.startswith('PATCHLEVEL ='):
+ kpatchlevel = int(line.split('=')[1].strip())
+ elif line.startswith('SUBLEVEL ='):
+ ksublevel = int(line.split('=')[1].strip())
+ elif line.startswith('EXTRAVERSION ='):
+ kextraversion = line.split('=')[1].strip()
+ version = ''
+ if kversion > -1 and kpatchlevel > -1:
+ version = '%d.%d' % (kversion, kpatchlevel)
+ if ksublevel > -1:
+ version += '.%d' % ksublevel
+ version += kextraversion
+ if version:
+ return version, op, 0, True
+ elif varname == 'SRC_URI':
+ while newlines[-1].startswith('#'):
+ del newlines[-1]
+ elif varname == 'COMPATIBLE_MACHINE':
+ while newlines[-1].startswith('#'):
+ del newlines[-1]
+ machine = tinfoil.config_data.getVar('MACHINE', True)
+ return machine, op, 0, True
+ return origvalue, op, 0, True
+ with open(template, 'r') as f:
+ varlist = ['SRCREV', 'SRCREV_machine', 'SRC_URI', 'LINUX_VERSION', 'COMPATIBLE_MACHINE']
+ (_, newlines) = bb.utils.edit_metadata(f, varlist, handle_var)
+ lines_before[:] = [line.rstrip('\n') for line in newlines]
+
+ return True
+
+def register_recipe_handlers(handlers):
+ handlers.append((KernelRecipeHandler(), 100))
diff --git a/yocto-poky/scripts/lib/recipetool/create_kmod.py b/yocto-poky/scripts/lib/recipetool/create_kmod.py
new file mode 100644
index 000000000..fe39edb28
--- /dev/null
+++ b/yocto-poky/scripts/lib/recipetool/create_kmod.py
@@ -0,0 +1,152 @@
+# Recipe creation tool - kernel module support plugin
+#
+# Copyright (C) 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
+from recipetool.create import RecipeHandler, read_pkgconfig_provides, validate_pv
+
+logger = logging.getLogger('recipetool')
+
+tinfoil = None
+
+def tinfoil_init(instance):
+ global tinfoil
+ tinfoil = instance
+
+
+class KernelModuleRecipeHandler(RecipeHandler):
+ def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
+ import bb.process
+ if 'buildsystem' in handled:
+ return False
+
+ module_inc_re = re.compile(r'^#include\s+<linux/module.h>$')
+ makefiles = []
+ is_module = False
+
+ makefiles = []
+
+ files = RecipeHandler.checkfiles(srctree, ['*.c', '*.h'], recursive=True)
+ if files:
+ for cfile in files:
+ # Look in same dir or parent for Makefile
+ for makefile in [os.path.join(os.path.dirname(cfile), 'Makefile'), os.path.join(os.path.dirname(os.path.dirname(cfile)), 'Makefile')]:
+ if makefile in makefiles:
+ break
+ else:
+ if os.path.exists(makefile):
+ makefiles.append(makefile)
+ break
+ else:
+ continue
+ with open(cfile, 'r') as f:
+ for line in f:
+ if module_inc_re.match(line.strip()):
+ is_module = True
+ break
+ if is_module:
+ break
+
+ if is_module:
+ classes.append('module')
+ handled.append('buildsystem')
+ # module.bbclass and the classes it inherits do most of the hard
+ # work, but we need to tweak it slightly depending on what the
+ # Makefile does (and there is a range of those)
+ # Check the makefile for the appropriate install target
+ install_lines = []
+ compile_lines = []
+ in_install = False
+ in_compile = False
+ install_target = None
+ with open(makefile, 'r') as f:
+ for line in f:
+ if line.startswith('install:'):
+ if not install_lines:
+ in_install = True
+ install_target = 'install'
+ elif line.startswith('modules_install:'):
+ install_lines = []
+ in_install = True
+ install_target = 'modules_install'
+ elif line.startswith('modules:'):
+ compile_lines = []
+ in_compile = True
+ elif line.startswith(('all:', 'default:')):
+ if not compile_lines:
+ in_compile = True
+ elif line:
+ if line[0] == '\t':
+ if in_install:
+ install_lines.append(line)
+ elif in_compile:
+ compile_lines.append(line)
+ elif ':' in line:
+ in_install = False
+ in_compile = False
+
+ def check_target(lines, install):
+ kdirpath = ''
+ manual_install = False
+ for line in lines:
+ splitline = line.split()
+ if splitline[0] in ['make', 'gmake', '$(MAKE)']:
+ if '-C' in splitline:
+ idx = splitline.index('-C') + 1
+ if idx < len(splitline):
+ kdirpath = splitline[idx]
+ break
+ elif install and splitline[0] == 'install':
+ if '.ko' in line:
+ manual_install = True
+ return kdirpath, manual_install
+
+ kdirpath = None
+ manual_install = False
+ if install_lines:
+ kdirpath, manual_install = check_target(install_lines, install=True)
+ if compile_lines and not kdirpath:
+ kdirpath, _ = check_target(compile_lines, install=False)
+
+ if manual_install or not install_lines:
+ lines_after.append('EXTRA_OEMAKE_append_task-install = " -C ${STAGING_KERNEL_DIR} M=${S}"')
+ elif install_target and install_target != 'modules_install':
+ lines_after.append('MODULES_INSTALL_TARGET = "install"')
+
+ warnmsg = None
+ kdirvar = None
+ if kdirpath:
+ res = re.match(r'\$\(([^$)]+)\)', kdirpath)
+ if res:
+ kdirvar = res.group(1)
+ if kdirvar != 'KERNEL_SRC':
+ lines_after.append('EXTRA_OEMAKE += "%s=${STAGING_KERNEL_DIR}"' % kdirvar)
+ elif kdirpath.startswith('/lib/'):
+ warnmsg = 'Kernel path in install makefile is hardcoded - you will need to patch the makefile'
+ if not kdirvar and not warnmsg:
+ warnmsg = 'Unable to find means of passing kernel path into install makefile - if kernel path is hardcoded you will need to patch the makefile'
+ if warnmsg:
+ warnmsg += '. Note that the variable KERNEL_SRC will be passed in as the kernel source path.'
+ logger.warn(warnmsg)
+ lines_after.append('# %s' % warnmsg)
+
+ return True
+
+ return False
+
+def register_recipe_handlers(handlers):
+ handlers.append((KernelModuleRecipeHandler(), 15))
diff --git a/yocto-poky/scripts/lib/recipetool/create_npm.py b/yocto-poky/scripts/lib/recipetool/create_npm.py
new file mode 100644
index 000000000..b3ffcdbc5
--- /dev/null
+++ b/yocto-poky/scripts/lib/recipetool/create_npm.py
@@ -0,0 +1,156 @@
+# Recipe creation tool - node.js NPM module support plugin
+#
+# Copyright (C) 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 os
+import logging
+import subprocess
+import tempfile
+import shutil
+import json
+from recipetool.create import RecipeHandler, split_pkg_licenses
+
+logger = logging.getLogger('recipetool')
+
+
+tinfoil = None
+
+def tinfoil_init(instance):
+ global tinfoil
+ tinfoil = instance
+
+
+class NpmRecipeHandler(RecipeHandler):
+ lockdownpath = None
+
+ def _handle_license(self, data):
+ '''
+ Handle the license value from an npm package.json file
+ '''
+ license = None
+ if 'license' in data:
+ license = data['license']
+ if isinstance(license, dict):
+ license = license.get('type', None)
+ return None
+
+ def _shrinkwrap(self, srctree, localfilesdir, extravalues, lines_before):
+ try:
+ runenv = dict(os.environ, PATH=tinfoil.config_data.getVar('PATH', True))
+ bb.process.run('npm shrinkwrap', cwd=srctree, stderr=subprocess.STDOUT, env=runenv, shell=True)
+ except bb.process.ExecutionError as e:
+ logger.warn('npm shrinkwrap failed:\n%s' % e.stdout)
+ return
+
+ tmpfile = os.path.join(localfilesdir, 'npm-shrinkwrap.json')
+ shutil.move(os.path.join(srctree, 'npm-shrinkwrap.json'), tmpfile)
+ extravalues.setdefault('extrafiles', {})
+ extravalues['extrafiles']['npm-shrinkwrap.json'] = tmpfile
+ lines_before.append('NPM_SHRINKWRAP := "${THISDIR}/${PN}/npm-shrinkwrap.json"')
+
+ def _lockdown(self, srctree, localfilesdir, extravalues, lines_before):
+ runenv = dict(os.environ, PATH=tinfoil.config_data.getVar('PATH', True))
+ if not NpmRecipeHandler.lockdownpath:
+ NpmRecipeHandler.lockdownpath = tempfile.mkdtemp('recipetool-npm-lockdown')
+ bb.process.run('npm install lockdown --prefix %s' % NpmRecipeHandler.lockdownpath,
+ cwd=srctree, stderr=subprocess.STDOUT, env=runenv, shell=True)
+ relockbin = os.path.join(NpmRecipeHandler.lockdownpath, 'node_modules', 'lockdown', 'relock.js')
+ if not os.path.exists(relockbin):
+ logger.warn('Could not find relock.js within lockdown directory; skipping lockdown')
+ return
+ try:
+ bb.process.run('node %s' % relockbin, cwd=srctree, stderr=subprocess.STDOUT, env=runenv, shell=True)
+ except bb.process.ExecutionError as e:
+ logger.warn('lockdown-relock failed:\n%s' % e.stdout)
+ return
+
+ tmpfile = os.path.join(localfilesdir, 'lockdown.json')
+ shutil.move(os.path.join(srctree, 'lockdown.json'), tmpfile)
+ extravalues.setdefault('extrafiles', {})
+ extravalues['extrafiles']['lockdown.json'] = tmpfile
+ lines_before.append('NPM_LOCKDOWN := "${THISDIR}/${PN}/lockdown.json"')
+
+ def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
+ import bb.utils
+ import oe
+ from collections import OrderedDict
+
+ if 'buildsystem' in handled:
+ return False
+
+ def read_package_json(fn):
+ with open(fn, 'r') as f:
+ return json.loads(f.read())
+
+ files = RecipeHandler.checkfiles(srctree, ['package.json'])
+ if files:
+ data = read_package_json(files[0])
+ if 'name' in data and 'version' in data:
+ extravalues['PN'] = data['name']
+ extravalues['PV'] = data['version']
+ classes.append('npm')
+ handled.append('buildsystem')
+ if 'description' in data:
+ lines_before.append('SUMMARY = "%s"' % data['description'])
+ if 'homepage' in data:
+ lines_before.append('HOMEPAGE = "%s"' % data['homepage'])
+
+ # Shrinkwrap
+ localfilesdir = tempfile.mkdtemp(prefix='recipetool-npm')
+ self._shrinkwrap(srctree, localfilesdir, extravalues, lines_before)
+
+ # Lockdown
+ self._lockdown(srctree, localfilesdir, extravalues, lines_before)
+
+ # Split each npm module out to is own package
+ npmpackages = oe.package.npm_split_package_dirs(srctree)
+ for item in handled:
+ if isinstance(item, tuple):
+ if item[0] == 'license':
+ licvalues = item[1]
+ break
+ if licvalues:
+ # Augment the license list with information we have in the packages
+ licenses = {}
+ license = self._handle_license(data)
+ if license:
+ licenses['${PN}'] = license
+ for pkgname, pkgitem in npmpackages.iteritems():
+ _, pdata = pkgitem
+ license = self._handle_license(pdata)
+ if license:
+ licenses[pkgname] = license
+ # Now write out the package-specific license values
+ # We need to strip out the json data dicts for this since split_pkg_licenses
+ # isn't expecting it
+ packages = OrderedDict((x,y[0]) for x,y in npmpackages.iteritems())
+ packages['${PN}'] = ''
+ pkglicenses = split_pkg_licenses(licvalues, packages, lines_after, licenses)
+ all_licenses = list(set([item for pkglicense in pkglicenses.values() for item in pkglicense]))
+ # Go back and update the LICENSE value since we have a bit more
+ # information than when that was written out (and we know all apply
+ # vs. there being a choice, so we can join them with &)
+ for i, line in enumerate(lines_before):
+ if line.startswith('LICENSE = '):
+ lines_before[i] = 'LICENSE = "%s"' % ' & '.join(all_licenses)
+ break
+
+ return True
+
+ return False
+
+def register_recipe_handlers(handlers):
+ handlers.append((NpmRecipeHandler(), 60))
diff --git a/yocto-poky/scripts/lib/recipetool/newappend.py b/yocto-poky/scripts/lib/recipetool/newappend.py
index 77b74cb73..bdf0693ec 100644
--- a/yocto-poky/scripts/lib/recipetool/newappend.py
+++ b/yocto-poky/scripts/lib/recipetool/newappend.py
@@ -25,18 +25,15 @@ import errno
import logging
import os
import re
+import subprocess
import sys
+import scriptutils
logger = logging.getLogger('recipetool')
tinfoil = None
-def plugin_init(pluginlist):
- # Don't need to do anything here right now, but plugins must have this function defined
- pass
-
-
def tinfoil_init(instance):
global tinfoil
tinfoil = instance
@@ -94,17 +91,21 @@ def newappend(args):
bb.utils.mkdirhier(os.path.dirname(append_path))
try:
- open(append_path, 'a')
+ open(append_path, 'a').close()
except (OSError, IOError) as exc:
logger.critical(str(exc))
return 1
- print(append_path)
+ if args.edit:
+ return scriptutils.run_editor([append_path, recipe_path])
+ else:
+ print(append_path)
-def register_command(subparsers):
+def register_commands(subparsers):
parser = subparsers.add_parser('newappend',
help='Create a bbappend for the specified target in the specified layer')
+ parser.add_argument('-e', '--edit', help='Edit the new append. This obeys $VISUAL if set, otherwise $EDITOR, otherwise vi.', action='store_true')
parser.add_argument('-w', '--wildcard-version', help='Use wildcard to make the bbappend apply to any recipe version', action='store_true')
parser.add_argument('destlayer', help='Base directory of the destination layer to write the bbappend to', type=layer)
parser.add_argument('target', help='Target recipe/provide to append')
diff --git a/yocto-poky/scripts/lib/recipetool/setvar.py b/yocto-poky/scripts/lib/recipetool/setvar.py
new file mode 100644
index 000000000..657d2b6a7
--- /dev/null
+++ b/yocto-poky/scripts/lib/recipetool/setvar.py
@@ -0,0 +1,75 @@
+# Recipe creation tool - set variable plugin
+#
+# Copyright (C) 2015 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 sys
+import os
+import argparse
+import glob
+import fnmatch
+import re
+import logging
+import scriptutils
+
+logger = logging.getLogger('recipetool')
+
+tinfoil = None
+plugins = None
+
+def tinfoil_init(instance):
+ global tinfoil
+ tinfoil = instance
+
+def setvar(args):
+ import oe.recipeutils
+
+ if args.delete:
+ if args.value:
+ logger.error('-D/--delete and specifying a value are mutually exclusive')
+ return 1
+ value = None
+ else:
+ if args.value is None:
+ logger.error('You must specify a value if not using -D/--delete')
+ return 1
+ value = args.value
+ varvalues = {args.varname: value}
+
+ if args.recipe_only:
+ patches = [oe.recipeutils.patch_recipe_file(args.recipefile, varvalues, patch=args.patch)]
+ else:
+ rd = oe.recipeutils.parse_recipe(args.recipefile, None, tinfoil.config_data)
+ if not rd:
+ return 1
+ patches = oe.recipeutils.patch_recipe(rd, args.recipefile, varvalues, patch=args.patch)
+ if args.patch:
+ for patch in patches:
+ for line in patch:
+ sys.stdout.write(line)
+ return 0
+
+
+def register_commands(subparsers):
+ parser_setvar = subparsers.add_parser('setvar',
+ help='Set a variable within a recipe',
+ description='Adds/updates the value a variable is set to in a recipe')
+ parser_setvar.add_argument('recipefile', help='Recipe file to update')
+ parser_setvar.add_argument('varname', help='Variable name to set')
+ parser_setvar.add_argument('value', nargs='?', help='New value to set the variable to')
+ parser_setvar.add_argument('--recipe-only', '-r', help='Do not set variable in any include file if present', action='store_true')
+ parser_setvar.add_argument('--patch', '-p', help='Create a patch to make the change instead of modifying the recipe', action='store_true')
+ parser_setvar.add_argument('--delete', '-D', help='Delete the specified value instead of setting it', action='store_true')
+ parser_setvar.set_defaults(func=setvar)