diff --git a/doc/manual/configuration.rst b/doc/manual/configuration.rst index f26894b4..a97949a2 100644 --- a/doc/manual/configuration.rst +++ b/doc/manual/configuration.rst @@ -1291,6 +1291,16 @@ The following settings are supported: | | | At the dependency both names will refer to the same | | | | tool. | +-------------+-----------------+-----------------------------------------------------+ +| inherit | Boolean | Inherit current environment, tools and sandbox to | +| | | this dependency. When set to ``false``, all | +| | | environment variables are reset to their default | +| | | and no tools or sandbox are passed down to the | +| | | dependency. This is mostly useful to make an | +| | | existing root-package become a dependency of | +| | | another (root) package. | +| | | | +| | | Default: ``true`` | ++-------------+-----------------+-----------------------------------------------------+ .. _configuration-recipes-env: diff --git a/pym/bob/input.py b/pym/bob/input.py index 7bcc38a2..c0c1f5a2 100644 --- a/pym/bob/input.py +++ b/pym/bob/input.py @@ -1855,10 +1855,11 @@ class Recipe(object): """ class Dependency(object): - def __init__(self, recipe, env, fwd, use, cond, tools, checkoutDep): + def __init__(self, recipe, env, fwd, use, cond, tools, checkoutDep, inherit): self.recipe = recipe self.envOverride = env self.provideGlobal = fwd + self.inherit = inherit self.use = use self.useEnv = "environment" in self.use self.useTools = "tools" in self.use @@ -1870,9 +1871,9 @@ def __init__(self, recipe, env, fwd, use, cond, tools, checkoutDep): self.checkoutDep = checkoutDep @staticmethod - def __parseEntry(dep, env, fwd, use, cond, tools, checkoutDep): + def __parseEntry(dep, env, fwd, use, cond, tools, checkoutDep, inherit): if isinstance(dep, str): - return [ Recipe.Dependency(dep, env, fwd, use, cond, tools, checkoutDep) ] + return [ Recipe.Dependency(dep, env, fwd, use, cond, tools, checkoutDep, inherit) ] else: envOverride = dep.get("environment") if envOverride: @@ -1884,6 +1885,7 @@ def __parseEntry(dep, env, fwd, use, cond, tools, checkoutDep): tools.update(toolOverride) fwd = dep.get("forward", fwd) use = dep.get("use", use) + inherit = dep.get("inherit", inherit) newCond = dep.get("if") if newCond is not None: cond = cond + [newCond] if cond is not None else [ newCond ] @@ -1892,22 +1894,22 @@ def __parseEntry(dep, env, fwd, use, cond, tools, checkoutDep): if name: if "depends" in dep: raise ParseError("A dependency must not use 'name' and 'depends' at the same time!") - return [ Recipe.Dependency(name, env, fwd, use, cond, tools, checkoutDep) ] + return [ Recipe.Dependency(name, env, fwd, use, cond, tools, checkoutDep, inherit) ] dependencies = dep.get("depends") if dependencies is None: raise ParseError("Either 'name' or 'depends' required for dependencies!") return Recipe.Dependency.parseEntries(dependencies, env, fwd, use, cond, tools, - checkoutDep) + checkoutDep, inherit) @staticmethod def parseEntries(deps, env={}, fwd=False, use=["result", "deps"], - cond=None, tools={}, checkoutDep=False): + cond=None, tools={}, checkoutDep=False, inherit=True): """Returns an iterator yielding all dependencies as flat list""" # return flattened list of dependencies return chain.from_iterable( Recipe.Dependency.__parseEntry(dep, env, fwd, use, cond, tools, - checkoutDep) + checkoutDep, inherit) for dep in deps ) @staticmethod @@ -2311,6 +2313,21 @@ def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None, env.setFunArgs({ "recipe" : self, "sandbox" : bool(sandbox) and sandboxEnabled, "__tools" : tools }) + thisDepEnv = depEnv + thisDepTools = depTools + thisDepDiffTools = depDiffTools + thisDepSandbox = depSandbox + thisDepDiffSandbox = depDiffSandbox + if not dep.inherit: + thisDepEnv = self.getRecipeSet().getRootEnv() + thisDepTools = Env() + # Compute the diff to remove all tools that were passed to the + # package. + thisDepDiffTools = { n : None for n in inputTools.inspect().keys() } + thisDepSandbox = None + # Clear sandbox, if any + thisDepDiffSandbox = None + recipe = env.substitute(dep.recipe, "dependency::"+dep.recipe) resolvedDeps.append(recipe) @@ -2319,20 +2336,17 @@ def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None, if dep.toolOverride: try: - thisDepTools = depTools.derive({ + thisDepTools = thisDepTools.derive({ k : depTools[v] for k,v in dep.toolOverride.items() }) except KeyError as e: raise ParseError("Cannot remap unkown tool '{}' for dependency '{}'!" .format(e.args[0], recipe)) - thisDepDiffTools = depDiffTools.copy() + thisDepDiffTools = thisDepDiffTools.copy() thisDepDiffTools.update({ k : depDiffTools.get(v, v) for k, v in dep.toolOverride.items() }) - else: - thisDepTools = depTools - thisDepDiffTools = depDiffTools - thisDepEnv = depEnv.derive( + thisDepEnv = thisDepEnv.derive( { key : env.substitute(value, "depends["+recipe+"].environment["+key+"]") for key, value in dep.envOverride.items() }) @@ -2342,11 +2356,11 @@ def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None, raise ParseError("Recipes are cyclic (1st package in cylce)") depStack = stack + [r.__packageName] p, s = r.prepare(thisDepEnv, sandboxEnabled, depStates, - depSandbox, thisDepTools, depStack) + thisDepSandbox, thisDepTools, depStack) subTreePackages.add(p.getName()) subTreePackages.update(s) depCoreStep = p.getCorePackageStep() - depRef = CoreRef(depCoreStep, [p.getName()], thisDepDiffTools, depDiffSandbox) + depRef = CoreRef(depCoreStep, [p.getName()], thisDepDiffTools, thisDepDiffSandbox) except ParseError as e: e.pushFrame(r.getPackageName()) raise e @@ -2365,7 +2379,7 @@ def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None, # Remember dependency diffs before changing them origDepDiffTools = thisDepDiffTools - origDepDiffSandbox = depDiffSandbox + origDepDiffSandbox = thisDepDiffSandbox # pick up various results of package for (n, s) in states.items(): @@ -3340,6 +3354,9 @@ async def getScmStatus(self): def getBuildHook(self, name): return self.__buildHooks.get(name) + def getRootEnv(self): + return self.__rootEnv + def getSandboxMounts(self): return self.__sandboxOpts.get("mount", []) @@ -3564,6 +3581,7 @@ def __createSchemas(self): schema.Optional('name') : str, schema.Optional('use') : useClauses, schema.Optional('forward') : bool, + schema.Optional('inherit') : bool, schema.Optional('environment') : VarDefineValidator("depends::environment"), schema.Optional('if') : schema.Or(str, IfExpression), schema.Optional('tools') : { toolNameSchema : toolNameSchema }, diff --git a/test/unit/test_input_recipeset.py b/test/unit/test_input_recipeset.py index 99427881..24fa69ee 100644 --- a/test/unit/test_input_recipeset.py +++ b/test/unit/test_input_recipeset.py @@ -647,6 +647,94 @@ def testCheckoutDepVariants(self): self.assertNotEqual(paVId, pbVId, "checkout steps are different") + def testDepInherit(self): + """ Test the `inherit` property for dependencies """ + + self.writeDefault( + {"environment" : {"DEFAULT" : "42" }}) + + self.writeClass("foo", """\ + root: True + depends: + - name: foo + use: [tools] + forward: True + packageTools: [foo] + """) + + self.writeRecipe("a", """\ + inherit: [foo] + depends: + - name: sandbox + use: [sandbox] + forward: True + - name: b-env + use: [environment] + forward: true + - name: b + inherit: False + - name: c + inherit: False + environment: + C: "1" + tools: + c: foo + - d + packageScript: "true" + """) + + self.writeRecipe("b-env", """\ + provideVars: + B: "2" + """) + + self.writeRecipe("b", """\ + inherit: [foo] + packageVars: [DEFAULT, B] + packageScript: "true" + """) + + self.writeRecipe("c", """\ + packageVars: [C] + packageTools: [c] + packageScript: "true" + """) + + self.writeRecipe("d", """\ + packageVars: [B] + packageTools: [foo] + packageScript: "true" + """) + + self.writeRecipe("foo", """\ + packageTools: [foo] + packageScript: "true" + provideTools: + foo: . + """) + + self.writeRecipe("sandbox", """\ + provideSandbox: + paths: ["."] + """) + + recipes = RecipeSet() + recipes.parse() + packages = recipes.generatePackages(lambda s,m: "unused", True) + + pa_b = packages.walkPackagePath("a/b") + pb = packages.walkPackagePath("b") + pb_e = packages.walkPackagePath("a/b-env") + pc = packages.walkPackagePath("a/c") + pd = packages.walkPackagePath("a/d") + self.assertEqual(pa_b.getPackageStep().getVariantId(), + pb.getPackageStep().getVariantId()) + self.assertEqual({"DEFAULT" : "42"}, pb.getPackageStep().getEnv()) + self.assertEqual({"C" : "1"}, pc.getPackageStep().getEnv()) + self.assertEqual(list(pc.getPackageStep().getTools()), ["c"]) + self.assertEqual(pc.getPackageStep().getSandbox(), None) + self.assertNotEqual(pb_e.getPackageStep().getSandbox(), None) + self.assertNotEqual(pd.getPackageStep().getSandbox(), None) class TestDependencyEnv(RecipesTmp, TestCase): """Tests related to "environment" block in dependencies"""