Repository: pantsbuild/pants

Issue:
Collection of resources not transitive
The `resources` arg on JVM and Python targets is now deprecated, but the behaviour of only collecting directly declared resources remains.

The result for the JVM is that resources are only collected transitively in the graph if they are directly depended on by a JvmTarget.

While this behaviour has existed for a while, it was difficult to run into it when resources were directly declared as "owned" by some particular target.


Discussion:

Resolving patch:
diff --git a/src/python/pants/backend/jvm/targets/jvm_target.py b/src/python/pants/backend/jvm/targets/jvm_target.py
--- a/src/python/pants/backend/jvm/targets/jvm_target.py
+++ b/src/python/pants/backend/jvm/targets/jvm_target.py
@@ -266,9 +266,8 @@ def provides(self):
 
   @property
   def resources(self):
-    # TODO(John Sirois): Consider removing this convenience:
-    #   https://github.com/pantsbuild/pants/issues/346
-    # TODO(John Sirois): Introduce a label and replace the type test?
+    # TODO: We should deprecate this method, but doing so will require changes to JVM publishing.
+    #   see https://github.com/pantsbuild/pants/issues/4568
     return [dependency for dependency in self.dependencies if isinstance(dependency, Resources)]
 
   @property
diff --git a/src/python/pants/backend/jvm/tasks/prepare_resources.py b/src/python/pants/backend/jvm/tasks/prepare_resources.py
--- a/src/python/pants/backend/jvm/tasks/prepare_resources.py
+++ b/src/python/pants/backend/jvm/tasks/prepare_resources.py
@@ -13,6 +13,8 @@
 from pants.backend.jvm.targets.jvm_target import JvmTarget
 from pants.backend.jvm.tasks.resources_task import ResourcesTask
 from pants.base.build_environment import get_buildroot
+from pants.build_graph.resources import Resources
+from pants.build_graph.target import Target
 from pants.util.dirutil import safe_mkdir
 
 
@@ -27,6 +29,10 @@ def __init__(self, *args, **kwargs):
     super(PrepareResources, self).__init__(*args, **kwargs)
     self._buildroot = get_buildroot()
 
+  @classmethod
+  def implementation_version(cls):
+    return super(PrepareResources, cls).implementation_version() + [('PrepareResources', 2)]
+
   def find_all_relevant_resources_targets(self):
     # NB: Ordering isn't relevant here, because it is applied during the dep walk to
     # consume from the runtime_classpath.
@@ -35,9 +41,9 @@ def is_jvm_target(target):
     jvm_targets = self.context.targets(predicate=is_jvm_target)
 
     all_resources_tgts = OrderedSet()
-    for target in jvm_targets:
-      if target.has_resources:
-        all_resources_tgts.update(target.resources)
+    for target in Target.closure_for_targets(jvm_targets, bfs=True):
+      if isinstance(target, Resources):
+        all_resources_tgts.add(target)
     return all_resources_tgts
 
   def prepare_resources(self, target, chroot):


Test patch:
diff --git a/tests/python/pants_test/backend/jvm/tasks/test_prepare_resources.py b/tests/python/pants_test/backend/jvm/tasks/test_prepare_resources.py
--- a/tests/python/pants_test/backend/jvm/tasks/test_prepare_resources.py
+++ b/tests/python/pants_test/backend/jvm/tasks/test_prepare_resources.py
@@ -60,6 +60,20 @@ def test_find_all_relevant_resources_targets(self):
     self.assertEqual(sorted([self.target('resources:target1'), self.target('resources:target4')]),
                      sorted(relevant_resources_targets))
 
+  def test_find_all_relevant_resources_targets_transitive(self):
+    # Insert a target alias between the resources and the jvm target.
+    resources_target = self.make_target('resources:target', target_type=Resources)
+    alias_target = self.make_target('alias:target',
+                                    target_type=Target,
+                                    dependencies=[resources_target])
+    jvm_target = self.make_target('jvm:target',
+                                  target_type=JvmTarget,
+                                  dependencies=[alias_target])
+
+    task = self.create_task(self.context(target_roots=[jvm_target]))
+    relevant_resources_targets = task.find_all_relevant_resources_targets()
+    self.assertEqual(sorted([resources_target]), sorted(relevant_resources_targets))
+
   def test_prepare_resources_none(self):
     task = self.create_task(self.context())
     resources = self.make_target('resources:target', target_type=Resources)