yocto-compat-layer.py: Add script to YP Compatible Layer validation
PATCH 1/1 yocto-compat-layer.py: Add script to YP Compatible Layer validation
The yocto-compat-layer script serves as a tool to validate the alignament of a layer with YP Compatible Layers Programme [1], is based on an RFC sent to the ML to enable automatic testing of layers [2] that wants to be YP Compatible.
The tool takes an layer (or set of layers) via command line option -l and detects what kind of layer is distro, machine or software and then executes a set of tests against the layer in order to validate the compatibility.
The tests currently implemented are:
common.test_readme: Test if a README file exists in the layer and isn't
empty.
common.test_parse: Test for execute bitbake -p without errors. common.test_show_environment: Test for execute bitbake -e without errors. common.test_signatures: Test executed in BSP and DISTRO layers to review
doesn't comes with recipes that changes the signatures.
bsp.test_bsp_defines_machines: Test if a BSP layers has machines
configurations.
bsp.test_bsp_no_set_machine: Test the BSP layer to doesn't set
machine at adding layer.
distro.test_distro_defines_distros: Test if a DISTRO layers has distro
configurations.
distro.test_distro_no_set_distro: Test the DISTRO layer to doesn't set
distro at adding layer.
Example of usage:
$ source oe-init-build-env $ yocto-compat-layer.py -l LAYER_DIR
[YOCTO #10596]
[1] https://www.yoctoproject.org/webform/yocto-project-compatible-registration
[2] https://lists.yoctoproject.org/pipermail/yocto-ab/2016-October/001801.html
--- scripts/lib/compatlayer/__init__.py | 160 ++++++++++++++++++++++++++++++ scripts/lib/compatlayer/case.py | 7 ++ scripts/lib/compatlayer/cases/__init__.py | 0 scripts/lib/compatlayer/cases/bsp.py | 26 +++++ scripts/lib/compatlayer/cases/common.py | 66 ++++++++++++ scripts/lib/compatlayer/cases/distro.py | 26 +++++ scripts/lib/compatlayer/context.py | 14 +++ scripts/yocto-compat-layer.py | 160 ++++++++++++++++++++++++++++++ 8 files changed, 459 insertions(+) create mode 100644 scripts/lib/compatlayer/__init__.py create mode 100644 scripts/lib/compatlayer/case.py create mode 100644 scripts/lib/compatlayer/cases/__init__.py create mode 100644 scripts/lib/compatlayer/cases/bsp.py create mode 100644 scripts/lib/compatlayer/cases/common.py create mode 100644 scripts/lib/compatlayer/cases/distro.py create mode 100644 scripts/lib/compatlayer/context.py create mode 100755 scripts/yocto-compat-layer.py
scripts/lib/compatlayer/__init__.py new file +160 -0
@@ -0,0 +1,160 @@ +# Yocto Project compatibility layer tool +# +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +import os +from enum import Enum + +class LayerType(Enum): + BSP = 0 + DISTRO = 1 + SOFTWARE = 2 + ERROR_NO_LAYER_CONF = 98 + ERROR_BSP_DISTRO = 99 + +def _get_configurations(path): + configs = [] + + for f in os.listdir(path): + file_path = os.path.join(path, f) + if os.path.isfile(file_path) and f.endswith('.conf'): + configs.append(f[:-5]) # strip .conf + return configs + +def _get_layer_collections(layer_path, lconf=None, data=None): + import bb.parse + import bb.data + + if lconf is None: + lconf = os.path.join(layer_path, 'conf', 'layer.conf') + + if data is None: + ldata = bb.data.init() + bb.parse.init_parser(ldata) + else: + ldata = data.createCopy() + + ldata.setVar('LAYERDIR', layer_path) + try: + ldata = bb.parse.handle(lconf, ldata, include=True) + except BaseException as exc: + raise LayerError(exc) + ldata.expandVarref('LAYERDIR') + + collections = (ldata.getVar('BBFILE_COLLECTIONS', True) or '').split() + if not collections: + name = os.path.basename(layer_path) + collections = [name] + + collections = {c: {} for c in collections} + for name in collections: + priority = ldata.getVar('BBFILE_PRIORITY_%s' % name, True) + pattern = ldata.getVar('BBFILE_PATTERN_%s' % name, True) + depends = ldata.getVar('LAYERDEPENDS_%s' % name, True) + collections[name]['priority'] = priority + collections[name]['pattern'] = pattern + collections[name]['depends'] = depends + + return collections + +def _detect_layer(layer_path): + """ + Scans layer directory to detect what type of layer + is BSP, Distro or Software. + + Returns a dictionary with layer name, type and path. + """ + + layer = {} + layer_name = os.path.basename(layer_path) + + layer['name'] = layer_name + layer['path'] = layer_path + layer['conf'] = {} + + if not os.path.isfile(os.path.join(layer_path, 'conf', 'layer.conf')): + layer['type'] = LayerType.ERROR_NO_LAYER_CONF + return layer + + machine_conf = os.path.join(layer_path, 'conf', 'machine') + distro_conf = os.path.join(layer_path, 'conf', 'distro') + + is_bsp = False + is_distro = False + + if os.path.isdir(machine_conf): + machines = _get_configurations(machine_conf) + if machines: + is_bsp = True + + if os.path.isdir(distro_conf): + distros = _get_configurations(distro_conf) + if distros: + is_distro = True + + if is_bsp and is_distro: + layer['type'] = LayerType.ERROR_BSP_DISTRO + elif is_bsp: + layer['type'] = LayerType.BSP + layer['conf']['machines'] = machines + elif is_distro: + layer['type'] = LayerType.DISTRO + layer['conf']['distros'] = distros + else: + layer['type'] = LayerType.SOFTWARE + + layer['collections'] = _get_layer_collections(layer['path']) + + return layer + +def detect_layers(directory): + layers = [] + + for root, dirs, files in os.walk(directory): + dir_name = os.path.basename(root) + + conf_dir = os.path.join(root, 'conf') + if dir_name.startswith('meta-') and os.path.isdir(conf_dir): + layer = _detect_layer(root) + if layer: + layers.append(layer) + + return layers + +def add_layer(bblayersconf, layer): + with open(bblayersconf, 'a+') as f: + f.write("\nBBLAYERS += \"%s\"\n" % layer['path']) + +def get_signatures(builddir, failsafe=False): + import subprocess + import re + + sigs = {} + + try: + cmd = 'bitbake ' + if failsafe: + cmd += '-k ' + cmd += '-S none world' + output = subprocess.check_output(cmd, shell=True, + stderr=subprocess.PIPE) + except subprocess.CalledProcessError as e: + import traceback + exc = traceback.format_exc() + msg = '%s\n%s\n' % (exc, e.output.decode('utf-8')) + raise RuntimeError(msg) + sigs_file = os.path.join(builddir, 'locked-sigs.inc') + + sig_regex = re.compile("^(?P<task>.*:.*):(?P<hash>.*) .$") + with open(sigs_file, 'r') as f: + for line in f.readlines(): + line = line.strip() + s = sig_regex.match(line) + if s: + sigs[s.group('task')] = s.group('hash') + + if not sigs: + raise RuntimeError('Can\'t load signatures from %s' % sigs_file) + + return sigs
scripts/lib/compatlayer/case.py new file +7 -0
@@ -0,0 +1,7 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +from oeqa.core.case import OETestCase + +class OECompatLayerTestCase(OETestCase): + pass
scripts/lib/compatlayer/cases/__init__.py new file +0 -0
scripts/lib/compatlayer/cases/bsp.py new file +26 -0
@@ -0,0 +1,26 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +import unittest + +from compatlayer import LayerType +from compatlayer.case import OECompatLayerTestCase + +class BSPCompatLayer(OECompatLayerTestCase): + @classmethod + def setUpClass(self): + if self.tc.layer['type'] != LayerType.BSP: + raise unittest.SkipTest("BSPCompatLayer: Layer %s isn't BSP one." %\ + self.tc.layer['name']) + + def test_bsp_defines_machines(self): + self.assertTrue(self.tc.layer['conf']['machines'], + "Layer is BSP but doesn't defines machines.") + + def test_bsp_no_set_machine(self): + from oeqa.utils.commands import get_bb_var + + machine = get_bb_var('MACHINE') + self.assertEqual(self.td['bbvars']['MACHINE'], machine, + msg="Layer %s modified machine %s -> %s" % \ + (self.tc.layer['name'], self.td['bbvars']['MACHINE'], machine))
scripts/lib/compatlayer/cases/common.py new file +66 -0
66 lines not on this page · raw message
scripts/lib/compatlayer/cases/distro.py new file +26 -0
@@ -0,0 +1,26 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +import unittest + +from compatlayer import LayerType +from compatlayer.case import OECompatLayerTestCase + +class DistroCompatLayer(OECompatLayerTestCase): + @classmethod + def setUpClass(self): + if self.tc.layer['type'] != LayerType.DISTRO: + raise unittest.SkipTest("DistroCompatLayer: Layer %s isn't Distro one." %\ + self.tc.layer['name']) + + def test_distro_defines_distros(self): + self.assertTrue(self.tc.layer['conf']['distros'], + "Layer is BSP but doesn't defines machines.") + + def test_distro_no_set_distros(self): + from oeqa.utils.commands import get_bb_var + + distro = get_bb_var('DISTRO') + self.assertEqual(self.td['bbvars']['DISTRO'], distro, + msg="Layer %s modified distro %s -> %s" % \ + (self.tc.layer['name'], self.td['bbvars']['DISTRO'], distro))
scripts/lib/compatlayer/context.py new file +14 -0
@@ -0,0 +1,14 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +import os +import sys +import glob +import re + +from oeqa.core.context import OETestContext + +class CompatLayerTestContext(OETestContext): + def __init__(self, td=None, logger=None, layer=None): + super(CompatLayerTestContext, self).__init__(td, logger) + self.layer = layer
scripts/yocto-compat-layer.py new file +160 -0
160 lines not on this page · raw message
Signature · 1 line
2.1.4
scripts/yocto-compat-layer.py line 51
Quoted 4 lines · Aníbal Limón
scripts/yocto-compat-layer.py new file +3
-0+ parser.add_argument('-l', '--layer', metavar='LAYER_DIR', + help='Layer to test compatibility with Yocto Project', + action='store', required=True)
The behavior of this argument is quite unintuitive. First, this path isn’t actually used as the layer directory, detect_layers searches down from that path to *find* layers in that subdirectory, so the argument isn’t what it says it is. Second, why are you making an option mandatory rather than just using arguments?
That is, why layer-compat-layer.py -l layerpath rather than layer-compat-layer.py layerpath. If you change the call to parser.add_argument(‘layer’, …) it’d change to the latter behavior.
Signature · 4 lines
Christopher Larson kergoth at gmail dot com Founder - BitBake, OpenEmbedded, OpenZaurus Senior Software Engineer, Mentor Graphics
Quoted 14 lines · Christopher Larson
scripts/yocto-compat-layer.py line 51
Quoted 4 lines · Aníbal Limón
scripts/yocto-compat-layer.py new file +3
-0+ parser.add_argument('-l', '--layer', metavar='LAYER_DIR', + help='Layer to test compatibility with Yocto Project', + action='store', required=True)The behavior of this argument is quite unintuitive. First, this path isn’t actually used as the layer directory,
detect_layerssearches down from that path to *find* layers in that subdirectory, so the argument isn’t what it says it is. Second, why are you making an option mandatory rather than just using arguments?
Quoted 4 lines
That is, why
layer-compat-layer.py -l layerpathrather thanlayer-compat-layer.py layerpath. If you change the call toparser.add_argument(‘layer’, …)it’d change to the latter behavior.
Worth noting that you could, and probably should, also use nargs=‘+’ to let the user specify multiple layers to check, rather than a single path, if you go the latter route, and if not, you could change it to an append style (for the ‘action’) argument to accomplish the same.
Signature · 4 lines
Christopher Larson kergoth at gmail dot com Founder - BitBake, OpenEmbedded, OpenZaurus Senior Software Engineer, Mentor Graphics
Quoted 22 lines · Christopher Larson
Quoted 14 lines · Christopher Larson
scripts/yocto-compat-layer.py line 51
Quoted 4 lines · Aníbal Limón
scripts/yocto-compat-layer.py new file +3
-0+ parser.add_argument('-l', '--layer', metavar='LAYER_DIR', + help='Layer to test compatibility with Yocto Project', + action='store', required=True)The behavior of this argument is quite unintuitive. First, this path isn’t actually used as the layer directory,
detect_layerssearches down from that path to *find* layers in that subdirectory, so the argument isn’t what it says it is. Second, why are you making an option mandatory rather than just using arguments?Quoted 4 lines
That is, why
layer-compat-layer.py -l layerpathrather thanlayer-compat-layer.py layerpath. If you change the call toparser.add_argument(‘layer’, …)it’d change to the latter behavior.
Quoted 4 lines
Worth noting that you could, and probably should, also use nargs=‘+’ to let the user specify multiple layers to check, rather than a single path, if you go the latter route, and if not, you could change it to an append style (for the ‘action’) argument to accomplish the same.
Yes, it's better to change the -l option to use a positional argument with nargs +.
The detect_layers functionality is used because i want to cover cases when the user clones a repository that comes with multiple layers like meta-openembedded.
Best regards,
alimon