Repository: BobBuildTool/bob

Issue:
Make dependencies available in package setp
**Is your feature request related to a problem? Please describe.**
Currently, the dependencies of a recipe are only available in the build step. In case the recipe just gathers the result of other recipes, this is wasteful because the aggregated result needs to be copied once again in the package step.

**Describe the solution you'd like**
Add a `packageDep` property to the `depends` entries, mirroring the `checkoutDep` property.

Hints from issue comments:

Solution patch:
diff --git a/doc/manpages/bob-query-recipe.rst b/doc/manpages/bob-query-recipe.rst
index 6155776..e11719c 100644
--- a/doc/manpages/bob-query-recipe.rst
+++ b/doc/manpages/bob-query-recipe.rst
@@ -17,12 +17,14 @@ Synopsis
 
     bob query-recipe [-h] [-D DEFINES] [-c CONFIGFILE]
                      [--sandbox | --slim-sandbox | --dev-sandbox | --strict-sandbox | --no-sandbox]
-                     package
+                     packages [packages ...]
 
 Description
 -----------
 
-Query recipe and class files of package.
+Print the involved recipe and class files of packages. Each file name is
+printed only once, even if multiple packages are specied that share source
+files.
 
 Options
 -------
diff --git a/doc/manual/configuration.rst b/doc/manual/configuration.rst
index d6ca8bc..e88aa68 100644
--- a/doc/manual/configuration.rst
+++ b/doc/manual/configuration.rst
@@ -548,7 +548,7 @@ a script for the same step are joined in front of this script in the order the
 inheritance is specified. The inheritance graph is traversed depth first and
 every class is included exactly once.
 
-Dependencies of the recipe are by default only available to the
+Dependencies of the recipe are generally only available to the
 ``buildScript``. The path to the previous step (checkout workspace for
 ``buildScript``, build workspace for ``packageScript``) is always passed in
 ``$1``. Other dependencies are available in the order in which they were
@@ -556,7 +556,8 @@ declared at the :ref:`configuration-recipes-depends` section of the recipe. If
 a dependencies ``checkoutDep`` flag is set to ``True`` it will also be
 available to the ``checkoutScript``. This should be used carefully as it makes
 the checkout of the recipe sources dependent on the result of another
-dependency.
+dependency. Enabling the :ref:`configuration-recipes-packagedepends` property
+will make the dependencies available to the package step too.
 
 During execution of the script only the environment variables SHELL, USER,
 TERM, HOME and anything that was declared via {checkout,build,package}Vars
@@ -1694,6 +1695,20 @@ packages that are built from a library: a ``-target`` packet that has the
 shared libraries needed during runtime and a ``-dev`` packet that has the
 header files and other needed files to link with this library.
 
+.. _configuration-recipes-packagedepends:
+
+packageDepends
+~~~~~~~~~~~~~~
+
+Type: Boolean
+
+Defaults to ``False``. If enabled, all dependencies that are named in the
+:ref:`configuration-recipes-depends` section are available during the package
+step execution too. By default, just the build step has access to them.
+
+Enabling this property is useful for recipes that only need a package script.
+Typical examples are recipes that create images or that just gather packages.
+
 .. _configuration-recipes-privateenv:
 
 privateEnvironment
diff --git a/pym/bob/cmds/misc.py b/pym/bob/cmds/misc.py
index 0df1645..f57abf2 100644
--- a/pym/bob/cmds/misc.py
+++ b/pym/bob/cmds/misc.py
@@ -268,7 +268,7 @@ are used:
 def doQueryRecipe(argv, bobRoot):
     parser = argparse.ArgumentParser(prog="bob query-recipe",
         description="Query recipe and class files of package.")
-    parser.add_argument('package', help="(Sub-)package to query")
+    parser.add_argument('packages', nargs='+', help="(Sub-)packages to query")
     parser.add_argument('-D', default=[], action='append', dest="defines",
         help="Override default environment variable")
     parser.add_argument('-c', dest="configFile", default=[], action='append',
@@ -292,9 +292,14 @@ def doQueryRecipe(argv, bobRoot):
     recipes = RecipeSet()
     recipes.setConfigFiles(args.configFile)
     recipes.parse(defines)
-    package = recipes.generatePackages(lambda s,m: "unused", args.sandbox).walkPackagePath(args.package)
+    packages = recipes.generatePackages(lambda s,m: "unused", args.sandbox)
+
+    result = set()
+    for p in args.packages:
+        for package in packages.queryPackagePath(p):
+            result.update(package.getRecipe().getSources())
 
-    for fn in package.getRecipe().getSources():
+    for fn in sorted(result):
         print(fn)
 
 def doInit(argv, bobRoot):
diff --git a/pym/bob/input.py b/pym/bob/input.py
index 67cacdc..5b3c224 100644
--- a/pym/bob/input.py
+++ b/pym/bob/input.py
@@ -2160,6 +2160,7 @@ class Recipe(object):
         self.__buildVars |= self.__checkoutVars
         self.__buildVarsWeak = set(recipe.get("buildVarsWeak", []))
         self.__buildVarsWeak |= self.__checkoutVarsWeak
+        self.__packageDepends = recipe.get("packageDepends")
         self.__packageVars = set(recipe.get("packageVars", []))
         self.__packageVars |= self.__buildVars
         self.__packageVarsWeak = set(recipe.get("packageVarsWeak", []))
@@ -2312,6 +2313,7 @@ class Recipe(object):
             if self.__shared is None: self.__shared = cls.__shared
             if self.__relocatable is None: self.__relocatable = cls.__relocatable
             if self.__jobServer is None: self.__jobServer = cls.__jobServer
+            if self.__packageDepends is None: self.__packageDepends = cls.__packageDepends
             tmp = cls.__provideTools.copy()
             tmp.update(self.__provideTools)
             self.__provideTools = tmp
@@ -2352,6 +2354,9 @@ class Recipe(object):
         if self.__jobServer is None:
             self.__jobServer = False
 
+        if self.__packageDepends is None:
+            self.__packageDepends = False
+
         # Optimize provideDeps
         self.__provideDeps = [ getProvideDepsResolver(d) for d in self.__provideDeps ]
 
@@ -2788,8 +2793,11 @@ class Recipe(object):
         packageDigestEnv = env.prune(self.__packageVars)
         packageEnv = ( env.prune(self.__packageVars | self.__packageVarsWeak)
             if self.__packageVarsWeak else packageDigestEnv )
+        packageDeps = [CoreRef(buildCoreStep)]
+        if self.__packageDepends:
+            packageDeps.extend(results)
         packageCoreStep = p.createCorePackageStep(self.__package, packageDigestEnv, packageEnv,
-            [CoreRef(buildCoreStep)], doFingerprint, toolDepPackage, toolDepPackageWeak)
+            packageDeps, doFingerprint, toolDepPackage, toolDepPackageWeak)
 
         # provide environment
         packageCoreStep.providedEnv = env.substituteCondDict(self.__provideVars, "provideVars")
@@ -4083,6 +4091,7 @@ class RecipeSet:
             schema.Optional('scriptLanguage') : schema.And(schema.Or("bash", "PowerShell"),
                                                            schema.Use(ScriptLanguage)),
             schema.Optional('jobServer') : bool,
+            schema.Optional('packageDepends') : bool,
         }
         for (name, prop) in self.__properties.items():
             classSchemaSpec[schema.Optional(name)] = schema.Schema(prop.validate,


Test patch:
diff --git a/test/unit/test_input_recipeset.py b/test/unit/test_input_recipeset.py
index db32092..833ae44 100644
--- a/test/unit/test_input_recipeset.py
+++ b/test/unit/test_input_recipeset.py
@@ -850,6 +850,32 @@ class TestDependencies(RecipesTmp, TestCase):
         self.assertNotEqual(pb_e.getPackageStep().getSandbox(), None)
         self.assertNotEqual(pd.getPackageStep().getSandbox(), None)
 
+    def testPackageDepends(self):
+        """Test that checkoutDepends enables dependencies in package step"""
+
+        self.writeRecipe("root", """\
+            root: True
+            depends:
+                - lib1
+                - lib2
+            packageDepends: True
+            packageScript: "true"
+            """)
+        self.writeRecipe("lib1", "")
+        self.writeRecipe("lib2", "")
+
+        recipes = RecipeSet()
+        recipes.parse()
+        packages = recipes.generatePackages(lambda x,y: "unused")
+        p = packages.walkPackagePath("root")
+        self.assertEqual(len(p.getPackageStep().getArguments()), 3)
+        self.assertEqual(p.getPackageStep().getArguments()[0].getPackage().getName(),
+                         "root")
+        self.assertEqual(p.getPackageStep().getArguments()[1].getPackage().getName(),
+                         "lib1")
+        self.assertEqual(p.getPackageStep().getArguments()[2].getPackage().getName(),
+                         "lib2")
+
 class TestDependencyEnv(RecipesTmp, TestCase):
     """Tests related to "environment" block in dependencies"""