Repository: pantsbuild/pants

Issue:
Export struct_deps, service_deps for scrooge synthetic thrift targets
Scrooge generated targets expose scrooge types to their consumers that are needed to use and compile against them. The synthetic target creation should add those targets to the synthetic targets exports.


Discussion:
Seems like it would be preferable to have explicit lists of `service_exports` and `struct_exports`, unless they're all really always necessary...?
That's a good question. I think splitting it out would make some sense. Because of trait type deps, you'll always need certain ones, but I'm not certain you would always need all of them.
Particularly since in the java case, you may not need as many. Scala thrift services certainly could do with them.
My inclination would be to add service_exports and struct_exports, defaulting to None

Resolving patch:
diff --git a/contrib/scrooge/src/python/pants/contrib/scrooge/tasks/scrooge_gen.py b/contrib/scrooge/src/python/pants/contrib/scrooge/tasks/scrooge_gen.py
--- a/contrib/scrooge/src/python/pants/contrib/scrooge/tasks/scrooge_gen.py
+++ b/contrib/scrooge/src/python/pants/contrib/scrooge/tasks/scrooge_gen.py
@@ -53,6 +53,12 @@ def register_options(cls, register):
     register('--structs-deps', default={}, advanced=True, type=dict,
              help='A map of language to targets to add as dependencies of '
                   'synthetic thrift libraries that contain structs.')
+    register('--service-exports', default={}, advanced=True, type=dict,
+             help='A map of language to targets to add as exports of '
+                  'synthetic thrift libraries that contain services.')
+    register('--structs-exports', default={}, advanced=True, type=dict,
+             help='A map of language to targets to add as exports of '
+                  'synthetic thrift libraries that contain structs.')
     register('--target-types',
              default={'scala': 'scala_library', 'java': 'java_library', 'android': 'java_library'},
              advanced=True,
@@ -293,6 +299,17 @@ def synthetic_target_extra_dependencies(self, target, target_workdir):
     deps.update(target.dependencies)
     return deps
 
+  def synthetic_target_extra_exports(self, target, target_workdir):
+    dep_info = self._resolved_export_info
+    target_declares_service = any(self._declares_service(source)
+                                  for source in target.sources_relative_to_buildroot())
+    language = self._thrift_defaults.language(target)
+
+    if target_declares_service:
+      return dep_info.service[language]
+    else:
+      return dep_info.structs[language]
+
   def _thrift_dependencies_for_target(self, target):
     dep_info = self._resolved_dep_info
     target_declares_service_or_exception = any(self._declares_service(source) or self._declares_exception(source)
@@ -309,6 +326,11 @@ def _resolved_dep_info(self):
     return ScroogeGen.DepInfo(self._resolve_deps(self.get_options().service_deps),
                               self._resolve_deps(self.get_options().structs_deps))
 
+  @memoized_property
+  def _resolved_export_info(self):
+    return ScroogeGen.DepInfo(self._resolve_deps(self.get_options().service_exports),
+                              self._resolve_deps(self.get_options().structs_exports))
+
   @property
   def _copy_target_attributes(self):
     return ['provides', 'strict_deps', 'fatal_warnings']


Test patch:
diff --git a/contrib/scrooge/tests/python/pants_test/contrib/scrooge/tasks/test_scrooge_gen_integration.py b/contrib/scrooge/tests/python/pants_test/contrib/scrooge/tasks/test_scrooge_gen_integration.py
--- a/contrib/scrooge/tests/python/pants_test/contrib/scrooge/tasks/test_scrooge_gen_integration.py
+++ b/contrib/scrooge/tests/python/pants_test/contrib/scrooge/tasks/test_scrooge_gen_integration.py
@@ -20,6 +20,52 @@ def run_pants(self, command, config=None, stdin_data=None, extra_env=None, **kwa
         'pythonpath': ["%(buildroot)s/contrib/scrooge/src/python"],
         'backend_packages': ["pants.backend.codegen", "pants.backend.jvm", "pants.contrib.scrooge"]
       },
+      'scala-platform': { 'version': '2.11' },
+      'gen.scrooge': {
+        'service_deps': {
+            'java': [
+              '3rdparty:slf4j-api',
+              '3rdparty:thrift-0.6.1',
+              '3rdparty/jvm/com/twitter:finagle-thrift',
+              '3rdparty/jvm/com/twitter:scrooge-core',
+            ],
+            'scala': [
+              '3rdparty:thrift-0.6.1',
+              '3rdparty/jvm/com/twitter:finagle-thrift',
+              '3rdparty/jvm/com/twitter:scrooge-core',
+            ],
+          },
+        'service_exports': {
+            'java': [
+              '3rdparty:thrift-0.6.1',
+            ],
+            'scala': [
+              '3rdparty:thrift-0.6.1',
+              '3rdparty/jvm/com/twitter:finagle-thrift',
+              '3rdparty/jvm/com/twitter:scrooge-core',
+            ]
+          },
+        'structs_deps': {
+            'java': [
+              '3rdparty:thrift-0.6.1',
+              '3rdparty/jvm/com/twitter:scrooge-core',
+            ],
+            'scala': [
+              '3rdparty:thrift-0.6.1',
+              '3rdparty/jvm/com/twitter:scrooge-core',
+            ],
+          },
+        'structs_exports': {
+            'java': [
+              '3rdparty:thrift-0.6.1',
+              '3rdparty/jvm/com/twitter:scrooge-core',
+            ],
+            'scala': [
+              '3rdparty:thrift-0.6.1',
+              '3rdparty/jvm/com/twitter:scrooge-core',
+            ],
+          }
+      }
     }
     if config:
       for scope, scoped_cfgs in config.items():
@@ -45,6 +91,13 @@ def test_both_compiler_args_and_rpc_style(self):
     pants_run = self.run_pants(cmd)
     self.assert_success(pants_run)
 
+  def test_exports_of_thrift(self):
+    # Compiling against a thrift service with strict_deps=True should work
+    # because the necessary transitive dependencies will be exported.
+    cmd = ['compile', 'contrib/scrooge/tests/scala/org/pantsbuild/contrib/scrooge/scrooge_gen']
+    pants_run = self.run_pants(cmd)
+    self.assert_success(pants_run)
+
   def test_namespace_map(self):
     # scrooge_gen should pass with namespace_map specified
     cmd = ['gen', self.thrift_test_target('namespace-map-thrift')]
diff --git a/contrib/scrooge/tests/scala/org/pantsbuild/contrib/scrooge/scrooge_gen/BUILD b/contrib/scrooge/tests/scala/org/pantsbuild/contrib/scrooge/scrooge_gen/BUILD
new file mode 100644
--- /dev/null
+++ b/contrib/scrooge/tests/scala/org/pantsbuild/contrib/scrooge/scrooge_gen/BUILD
@@ -0,0 +1,6 @@
+scala_library(
+  dependencies=[
+    'contrib/scrooge/tests/thrift/org/pantsbuild/contrib/scrooge/scrooge_gen:good-thrift'
+  ],
+  strict_deps=True
+)
diff --git a/contrib/scrooge/tests/scala/org/pantsbuild/contrib/scrooge/scrooge_gen/UseGoodThrift.scala b/contrib/scrooge/tests/scala/org/pantsbuild/contrib/scrooge/scrooge_gen/UseGoodThrift.scala
new file mode 100644
--- /dev/null
+++ b/contrib/scrooge/tests/scala/org/pantsbuild/contrib/scrooge/scrooge_gen/UseGoodThrift.scala
@@ -0,0 +1,9 @@
+package org.pantsbuild.contrib.scrooge.scrooge_gen
+
+// NOTE: FutureIface is deprecated in a later version of scrooge according to
+//       https://twitter.github.io/scrooge/Finagle.html#creating-a-server
+class UseGoodThrift extends all.your.base.thriftscala.MyService.FutureIface {
+  def getNumber(x: Int): com.twitter.util.Future[Int] = {
+    com.twitter.util.Future[Int](1)
+  }
+}