@@ -288,8 +288,10 @@ def fork_off_task(cfg, data, databuilder, workerdata, extraconfigdata, runtask):
else:
logger.debug("Skipping disable network for %s since %s is not a local uid." % (taskname, uid))
- # exported_vars() returns a generator which *cannot* be passed to os.environ.update()
- # successfully. We also need to unset anything from the environment which shouldn't be there
+ # exported_vars() expands each value, so it must be called before
+ # empty_environment() below: an expansion that shells out needs the
+ # environment bitbake was started with. We also need to unset
+ # anything from the environment which shouldn't be there
exports = bb.data.exported_vars(the_data)
bb.utils.empty_environment()
@@ -193,6 +193,12 @@ def exported_keys(d):
not bb.utils.to_boolean(d.getVarFlag(key, 'unexport')))
def exported_vars(d):
+ """Return the exported variables as a list of (key, value) pairs.
+
+ Every value is expanded before returning, so a caller that changes the
+ environment afterwards still gets what the expansion saw.
+ """
+ exported = []
k = list(exported_keys(d))
for key in k:
try:
@@ -202,7 +208,9 @@ def exported_vars(d):
continue
if value is not None:
- yield key, str(value)
+ exported.append((key, str(value)))
+
+ return exported
def emit_func(func, o=sys.__stdout__, d = init()):
"""Emits all items in the data store in a format such that it can be sourced by a shell."""
@@ -723,3 +723,31 @@ class EmitVar(unittest.TestCase):
self.assertEqual(self.get_output(out), ['bad_chars="a\\"b \\',
'c\\`d \\',
'e\\$f"'])
+
+class ExportedVars(unittest.TestCase):
+ def test_expanded_before_returning(self):
+ # Called while the variable is set, read once it is gone: the value
+ # has to be the one from the call, not from the read.
+ d = bb.data.init()
+ d.setVar("TESTVAR", "${@os.environ.get('BB_TEST_EXPORT', 'gone')}")
+ d.setVarFlag("TESTVAR", "export", "1")
+
+ with bb.utils.environment(BB_TEST_EXPORT="present"):
+ exported = bb.data.exported_vars(d)
+
+ self.assertEqual(dict(exported), {"TESTVAR": "present"})
+ self.assertIsInstance(exported, list)
+
+ def test_unexpandable_value_warns_and_is_skipped(self):
+ # The warning belongs to the call, not to a later iteration.
+ d = bb.data.init()
+ d.setVar("TESTVAR", "value")
+ d.setVarFlag("TESTVAR", "export", "1")
+ d.setVar("TESTBROKEN", "${@int('not a number')}")
+ d.setVarFlag("TESTBROKEN", "export", "1")
+
+ with LogRecord() as logs:
+ exported = bb.data.exported_vars(d)
+
+ self.assertEqual(dict(exported), {"TESTVAR": "value"})
+ self.assertTrue(logContains("Unable to export ${TESTBROKEN}", logs))