TipsAndTricks/Tinfoil

NOTE: This is currently a draft, needs additional content & editing - PaulEggleton (talk) 18:22, 30 May 2017 (PDT)
= Writing simple utility scripts using Tinfoil =
The tinfoil API, which was introduced to BitBake a number of years ago and underwent a major rework in the 2.3 (pyro) release, provides a way for you to write python scripts to interact with the metadata and call into BitBake/OpenEmbedded code. It's intended to be an easier-to-use wrapper around BitBake's internal code, hence the name. Some of the scripts that come with BitBake / OpenEmbedded make heavy use of it - bitbake-layers, devtool, recipetool and oe-pkgdata-util are examples.
== Preamble - loading tinfoil ==
There's a bit of mostly boilerplate that needs to be at the start of any script using tinfoil. This is written assuming the script will be placed in the scripts subdirectory, although you can modify the import code to make it work from anywhere:
#!/usr/bin/env python3
import os
import sys
# Set up sys.path to let us import tinfoil
scripts_path = os.path.dirname(os.path.realpath(__file__))
lib_path = scripts_path + '/lib'
sys.path.insert(0, lib_path)
import scriptpath
scriptpath.add_bitbake_lib_path()
import bb.tinfoil
== Getting a variable value ==
Let's take a look at a simple example which reads a configuration-level variable value - TMPDIR to be specific (I've omitted the boilerplate above):
with bb.tinfoil.Tinfoil() as tinfoil:
 tinfoil.prepare(config_only=True)
 tmpdir = tinfoil.config_data.getVar('TMPDIR')
 print('TMPDIR is "%s"' % tmpdir)
If you are querying multiple variable values this is much more efficient than calling out to "bitbake -e" and parsing its output, which was the old way of getting this kind of information.
== Parsing recipes ==
Here's another example where we parse a couple of recipes:
with bb.tinfoil.Tinfoil() as tinfoil:
 tinfoil.prepare(config_only=False)
 # Parse the gzip recipe
 rd = tinfoil.parse_recipe('gzip')
 print('gzip SRC_URI = "%s"' % rd.getVar('SRC_URI'))
 # Parse the kernel recipe (whichever is selected in your configuration)
 rd = tinfoil.parse_recipe('virtual/kernel')
 print('Kernel recipe is %s' % rd.getVar('PN'))
 # Check if the kernel recipe inherits the kernel class - we'd expect True
 print('Kernel recipe inherits kernel class: %s' % bb.data.inherits_class('kernel', rd))
 # Check if the kernel recipe inherits the cmake class - we'd expect False
 print('Kernel recipe inherits cmake class: %s' % bb.utils.inherits_class('cmake', rd))
Notice that in this second example we pass config_only=False - this tells tinfoil to load the data for all recipes enabled in your configuration, just as bitbake does every time you run a normal build. You don't have to specify config_only=False to parse a recipe file if you know the path to it, but it is required if you want to specify it simply by name and get the preferred version / provider.
The parse_recipe() function will accept recipe names, or providers such as virtual/kernel in the second call above. It returns a datastore (usually seen as d within recipes and other python code within OE) which you can then use to get variable values, check inheritance etc.
== Enumerating available recipes ==
Iterating over available recipes in the system is relatively straightforward.
=== 2.4+ ===
with bb.tinfoil.Tinfoil() as tinfoil:
 tinfoil.prepare(config_only=False)
 for recipe in tinfoil.all_recipes():
 print(recipe.pn)
tinfoil.all_recipes() returns an iterator over TinfoilRecipeInfo objects. These have the following attributes/functions:
Attribute/function	Purpose
pn 	Recipe name (PN)
pe 	Recipe epoch (PE)
pv 	Recipe version (PV)
pr 	Recipe revision (PR)
fn 	Recipe file name of the default provider (full path with virtual prefix if applicable)
fns 	All recipe files which provide this same PN
inherits() 	Classes which the recipe inherits
inherit_files 	Class files which the recipe inherits
depends 	Build-time dependencies for this recipe (derived from DEPENDS)
provides 	Build-time provides for this recipe (derived from PROVIDES)
alternates 	Other recipe files which provide this same pn (same as inherit_files, excluding the default provider)
rdepends 	Runtime dependencies of this recipe (RDEPENDS)
rrecommends 	Runtime recommends of this recipe (RRECOMMENDS)
rprovides 	Runtime provides of this recipe (RPROVIDES)
packages 	Runtime packages for this recipe (PACKAGES)
packages_dynamic 	Runtime dynamic package expressions assumed satisfied by this recipe (PACKAGES_DYNAMIC)
=== Pre-2.4 ===
with bb.tinfoil.Tinfoil() as tinfoil:
 tinfoil.prepare(config_only=False)
 for pn in tinfoil.cooker.recipecaches[""].pkg_pn:
 print(pn)
A few notes about pkg_pn:
* It's actually a dictionary and so the keys aren't sorted alphabetically
* It contains every target, not just every recipe file - i.e. there will be variants created through BBCLASSEXTEND in there as well.
* There's a corresponding pkg_fn dict that maps files to PN values, but bear in mind this also contains virtual:... entries, again created through BBCLASSEXTEND.
* This doesn't yet work with multiconfig - just the main configuration
There are some improvements to be made to the API in this area which should hopefully land in 2.4.
== Other useful functions ==
Tinfoil also gives you access to quite a bit of BitBake and OE utility code - in particular have a look in bitbake/lib/bb/utils.py, meta/lib/oe/utils.py, and meta/lib/oe/recipeutils.py as they all contain useful functions that will help you perform various tasks that might be interesting from a script that uses tinfoil.