From ff60e4eda5fd131f4c47ec1bb78853b64390a086 Mon Sep 17 00:00:00 2001 From: Ralf Hubert Date: Wed, 22 May 2024 08:32:46 +0000 Subject: [PATCH] input: add 'inherit' dependency property This property controls the inheritance of environment, tools and sandbox for a dependency. It defaults to true so everything is inherited. If set to `false` environment, tools and sandbox are dropped. The dependency starts with the default-environment, no tools and no sandbox. This becomes particularly useful when an existing root-package should become a dependency of another root-package, e.g. for building an installer. --- doc/manual/configuration.rst | 9 ++++ pym/bob/input.py | 52 ++++++++++++------ test/unit/test_input_recipeset.py | 88 +++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 16 deletions(-) diff --git a/doc/manual/configuration.rst b/doc/manual/configuration.rst index f26894b4..9fa2b769 100644 --- a/doc/manual/configuration.rst +++ b/doc/manual/configuration.rst @@ -1291,6 +1291,15 @@ The following settings are supported: | | | At the dependency both names will refer to the same | | | | tool. | +-------------+-----------------+-----------------------------------------------------+ +| inherit | Boolean | Inherit environment and tools available for this | +| | | dependency. | +| | | When set to "false" all the environment and tools | +| | | are dropped. 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..9723ffaf 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 @@ -2308,7 +2310,21 @@ def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None, resolvedDeps = [] for dep in self.__deps: - env.setFunArgs({ "recipe" : self, "sandbox" : bool(sandbox) and sandboxEnabled, + thisDepEnv = depEnv + thisDepTools = depTools + thisDepDiffTools = depDiffTools + thisSandbox = sandbox + thisSandboxEnabled = sandboxEnabled + thisDepDiffSandbox = depDiffSandbox + if not dep.inherit: + thisDepEnv = self.getRecipeSet().getRootEnv() + thisDepTools = Env() + thisDepDiffTools = {} + thisDepDiffSandbox = ... + thisSandbox = None + thisSandboxEnabled = False + + env.setFunArgs({ "recipe" : self, "sandbox" : bool(thisSandbox) and thisSandboxEnabled, "__tools" : tools }) recipe = env.substitute(dep.recipe, "dependency::"+dep.recipe) @@ -2319,20 +2335,20 @@ 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 + thisDepTools = thisDepTools + thisDepDiffTools = thisDepDiffTools - thisDepEnv = depEnv.derive( + thisDepEnv = thisDepEnv.derive( { key : env.substitute(value, "depends["+recipe+"].environment["+key+"]") for key, value in dep.envOverride.items() }) @@ -2341,12 +2357,12 @@ def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None, if r.__packageName in stack: raise ParseError("Recipes are cyclic (1st package in cylce)") depStack = stack + [r.__packageName] - p, s = r.prepare(thisDepEnv, sandboxEnabled, depStates, + p, s = r.prepare(thisDepEnv, thisSandboxEnabled, depStates, depSandbox, 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 +2381,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 +3356,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 +3583,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"""