diff mbox series

[layerindex-web,2/2] update_layer: make addpylib libraries available to recipe parsing

Message ID 20261114044035.88470-3-tim.orling@konsulko.com
State Under Review
Delegated to: Tim Orling
Headers show
Series Fix parsing of layers like meta-luneos | expand

Commit Message

Tim Orling Nov. 14, 2026, 4:40 a.m. UTC
A recipe that uses a Python library added with addpylib in its layer's
conf/layer.conf failed to parse, e.g. meta-luneos with "NameError: name
'oebackport' is not defined". update_layer.py parses layer.conf itself,
but the recipes are parsed by the BitBake server, which only processes
addpylib in its base configuration, so the library was never imported
there. Add the addpylib statements of the layer and its dependencies to
the bblayers.conf the server reads.

[YOCTO #16192]

AI-Generated: Claude Opus 5.5
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
 layerindex/recipeparse.py  | 23 ++++++++++++++++++++++-
 layerindex/update_layer.py |  9 ++++++++-
 tests/test_update.py       | 20 ++++++++++++++++++++
 3 files changed, 50 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/layerindex/recipeparse.py b/layerindex/recipeparse.py
index d8486e7..4d91d77 100644
--- a/layerindex/recipeparse.py
+++ b/layerindex/recipeparse.py
@@ -24,7 +24,24 @@  class RecipeParseError(Exception):
 
 
 
-def init_parser(settings, branch, bitbakepath, enable_tracking=False, nocheckout=False, classic=False, logger=None):
+def layer_addpylibs(layerdir):
+    """
+    Return the addpylib statements in a layer's conf/layer.conf, with LAYERDIR
+    expanded. The BitBake server only processes addpylib while parsing its base
+    configuration, so these need to go in bblayers.conf for its recipe parsing
+    to be able to use the libraries.
+    """
+    lines = []
+    try:
+        with open(os.path.join(layerdir, 'conf', 'layer.conf')) as f:
+            for line in f:
+                if re.match(r'\s*addpylib\s', line):
+                    lines.append(line.strip().replace('${LAYERDIR}', layerdir))
+    except FileNotFoundError:
+        pass
+    return lines
+
+def init_parser(settings, branch, bitbakepath, enable_tracking=False, nocheckout=False, classic=False, logger=None, pylib_layerdirs=None):
     if not (nocheckout or classic):
         # Check out the branch of BitBake appropriate for this branch and clean out any stale files (e.g. *.pyc)
         if re.match('[0-9a-f]{40}', branch.bitbake_branch):
@@ -86,6 +103,10 @@  def init_parser(settings, branch, bitbakepath, enable_tracking=False, nocheckout
             # We need this to avoid problems with AVAILABLE_LICENSES
             f.write('COREBASE = "%s"\n' % core_repodir)
             f.write('BBLAYERS = "%s"\n' % core_layerdir)
+            # Python libraries of the layer being parsed and its dependencies
+            for layerdir in pylib_layerdirs or []:
+                for line in layer_addpylibs(layerdir):
+                    f.write(line + '\n')
         pass
 
     if logger:
diff --git a/layerindex/update_layer.py b/layerindex/update_layer.py
index 3f21e8b..7e47269 100644
--- a/layerindex/update_layer.py
+++ b/layerindex/update_layer.py
@@ -428,8 +428,15 @@  def main():
                             logger.warn("Unable to checkout dependent layer %s - %s" % (layerdependency.dependency, str(e)))
 
                 logger.info("Collecting data for layer %s on branch %s" % (layer.name, branchdesc))
+                # Layers whose addpylib libraries recipes may use (the core
+                # layer's are loaded through BBLAYERS)
+                pylib_layerdirs = [layerdir]
+                for dep in layerbranch.dependencies_set.all():
+                    deplayerbranch = dep.dependency.get_layerbranch(branch.name)
+                    if deplayerbranch and dep.dependency.name != settings.CORE_LAYER_NAME:
+                        pylib_layerdirs.append(os.path.join(fetchdir, dep.dependency.get_fetch_dir(), deplayerbranch.vcs_subdir))
                 try:
-                    (tinfoil, tempdir) = recipeparse.init_parser(settings, branch, bitbakepath, nocheckout=options.nocheckout, logger=logger)
+                    (tinfoil, tempdir) = recipeparse.init_parser(settings, branch, bitbakepath, nocheckout=options.nocheckout, logger=logger, pylib_layerdirs=pylib_layerdirs)
                 except recipeparse.RecipeParseError as e:
                     logger.error(str(e))
                     sys.exit(1)
diff --git a/tests/test_update.py b/tests/test_update.py
index a0ac897..702a042 100644
--- a/tests/test_update.py
+++ b/tests/test_update.py
@@ -144,6 +144,26 @@  def test_upgrade_recipe(import_layer, repo, db_access_without_rollback_and_trunc
     assert recipe.filename == 'upgrademe_0.2.bb'
     assert recipe.filepath == 'recipes-example/upgrademe'
 
+def test_addpylib_used_in_recipe(import_layer, repo, db_access_without_rollback_and_truncate):
+    # [YOCTO #16192] a recipe can use a Python library the layer adds with
+    # addpylib in conf/layer.conf, as it can in a build
+    from layerindex.models import LayerBranch, Recipe
+    layerdir = os.path.join(repo, 'meta-layerindex-test')
+    os.makedirs(os.path.join(layerdir, 'lib', 'layerindextestlib'))
+    with open(os.path.join(layerdir, 'lib', 'layerindextestlib', '__init__.py'), 'w') as f:
+        f.write('def summary():\n    return "Summary from addpylib"\n')
+    with open(os.path.join(layerdir, 'conf', 'layer.conf'), 'a') as f:
+        f.write('\naddpylib ${LAYERDIR}/lib layerindextestlib\n')
+    os.makedirs(os.path.join(layerdir, 'recipes-example', 'pylib'))
+    with open(os.path.join(layerdir, 'recipes-example', 'pylib', 'pylib_1.0.bb'), 'w') as f:
+        f.write('SUMMARY = "${@layerindextestlib.summary()}"\nLICENSE = "MIT"\n')
+    run_cmd('git add -A && git commit -m "Add a recipe that uses addpylib"', cwd=repo)
+    run_cmd("layerindex/update.py -d -l meta-layerindex-test --nofetch --nocheckout")
+    layerbranch = LayerBranch.objects.get(layer__name='meta-layerindex-test')
+    recipe = Recipe.objects.filter(layerbranch=layerbranch, pn='pylib').first()
+    assert recipe, 'pylib recipe not found'
+    assert recipe.summary == 'Summary from addpylib'
+
 def test_add_recipe(import_layer, repo, db_access_without_rollback_and_truncate):
     from layerindex.models import LayerBranch, Recipe
     layerbranch = LayerBranch.objects.get(layer__name='meta-layerindex-test')