Move j2objc helper scripts into open-source Bazel.

--
MOS_MIGRATED_REVID=100490916
diff --git a/tools/BUILD b/tools/BUILD
index 4aa82c9..8e1ea84 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -12,6 +12,7 @@
         "//tools/jdk:srcs",
         "//tools/genrule:srcs",
         "//tools/cpp:srcs",
+        "//tools/j2objc:srcs",
         "//tools/objc:srcs",
         "//tools/test:srcs",
         "//tools/python:srcs",
@@ -26,6 +27,7 @@
         "//tools/jdk:package-srcs",
         "//tools/genrule:srcs",
         "//tools/cpp:srcs",
+        "//tools/j2objc:srcs",
         "//tools/objc:srcs",
         "//tools/test:srcs",
         "//tools/python:srcs",
diff --git a/tools/j2objc/BUILD b/tools/j2objc/BUILD
new file mode 100644
index 0000000..b0f68cc
--- /dev/null
+++ b/tools/j2objc/BUILD
@@ -0,0 +1,13 @@
+package(default_visibility = ["//visibility:public"])
+
+exports_files(glob(["**"]))
+
+filegroup(
+    name = "srcs",
+    srcs = glob(["**"]),
+)
+
+py_library(
+    name = "j2objc_wrapper",
+    srcs = ["j2objc_wrapper.py"],
+)
diff --git a/tools/j2objc/j2objc_wrapper.py b/tools/j2objc/j2objc_wrapper.py
new file mode 100755
index 0000000..c3e5778
--- /dev/null
+++ b/tools/j2objc/j2objc_wrapper.py
@@ -0,0 +1,163 @@
+#!/usr/bin/python2.7
+
+# Copyright 2015 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""A wrapper script for J2ObjC transpiler.
+
+This script wraps around J2ObjC transpiler to also output a dependency mapping
+file by scanning the import and include directives of the J2ObjC-translated
+files.
+"""
+
+import argparse
+import multiprocessing
+import os
+import Queue
+import re
+import subprocess
+import threading
+
+INCLUDE_RE = re.compile('#(include|import) "([^"]+)"')
+
+
+def RunJ2ObjC(java, jvm_flags, j2objc, main_class, flags):
+  """Runs J2ObjC transpiler to translate Java source files to ObjC.
+
+  Args:
+    java: The path of the Java executable.
+    jvm_flags: A comma-separated list of flags to pass to JVM.
+    j2objc: The deploy jar of J2ObjC.
+    main_class: The J2ObjC main class to invoke.
+    flags: A list of flags to pass to J2ObjC transpiler.
+  Returns:
+    None.
+  """
+  j2objc_args = [java]
+  j2objc_args.extend(filter(None, jvm_flags.split(',')))
+  j2objc_args.extend(['-cp', j2objc, main_class])
+  j2objc_args.extend(flags)
+
+  subprocess.check_call(j2objc_args, stderr=subprocess.STDOUT)
+
+
+def WriteDepMappingFile(translated_source_files,
+                        objc_file_path,
+                        output_dependency_mapping_file,
+                        file_open=open):
+  """Scans J2ObjC-translated files and outputs a dependency mapping file.
+
+  The mapping file contains mappings between translated source files and their
+  imported source files scanned from the import and include directives.
+
+  Args:
+    translated_source_files: A comma-separated list of files translated by
+        J2ObjC.
+    objc_file_path: The file path which represents a directory where the
+        generated ObjC files reside.
+    output_dependency_mapping_file: The path of the dependency mapping file to
+        write to.
+    file_open: Reference to the builtin open function so it may be
+        overridden for testing.
+  Returns:
+    None.
+  """
+  dep_mapping = dict()
+  input_file_queue = Queue.Queue()
+  output_dep_mapping_queue = Queue.Queue()
+  for output_file in translated_source_files.split(','):
+    input_file_queue.put(output_file)
+
+  for _ in xrange(multiprocessing.cpu_count()):
+    t = threading.Thread(target=_ReadDepMapping, args=(input_file_queue,
+                                                       output_dep_mapping_queue,
+                                                       objc_file_path,
+                                                       file_open))
+    t.start()
+
+  input_file_queue.join()
+
+  while not output_dep_mapping_queue.empty():
+    entry_file, deps = output_dep_mapping_queue.get()
+    dep_mapping[entry_file] = deps
+
+  f = file_open(output_dependency_mapping_file, 'w')
+  for entry in sorted(dep_mapping):
+    for dep in dep_mapping[entry]:
+      f.write(entry + ':' + dep + '\n')
+  f.close()
+
+
+def _ReadDepMapping(input_file_queue, output_dep_mapping_queue, objc_file_path,
+                    file_open=open):
+  while True:
+    try:
+      input_file = input_file_queue.get_nowait()
+    except Queue.Empty:
+      return
+    deps = []
+    entry = os.path.relpath(os.path.splitext(input_file)[0], objc_file_path)
+    with file_open(input_file, 'r') as f:
+      for line in f:
+        include = INCLUDE_RE.match(line)
+        if include:
+          include_path = include.group(2)
+          dep = os.path.splitext(include_path)[0]
+          if dep != entry:
+            deps.append(dep)
+    output_dep_mapping_queue.put((entry, deps))
+    input_file_queue.task_done()
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '--java',
+      required=True,
+      help='The path to the Java executable.')
+  parser.add_argument(
+      '--jvm_flags',
+      default='',
+      help='A comma-separated list of flags to pass to the JVM.')
+  parser.add_argument(
+      '--j2objc',
+      required=True,
+      help='The path to the J2ObjC deploy jar.')
+  parser.add_argument(
+      '--main_class',
+      required=True,
+      help='The main class of the J2ObjC deploy jar to execute.')
+  parser.add_argument(
+      '--translated_source_files',
+      required=True,
+      help=('A comma-separated list of file paths where J2ObjC will write the '
+            'translated files to.'))
+  parser.add_argument(
+      '--output_dependency_mapping_file',
+      required=True,
+      help='The file path of the dependency mapping file to write to.')
+  parser.add_argument(
+      '--objc_file_path',
+      required=True,
+      help=('The file path which represents a directory where the generated '
+            'ObjC files reside.'))
+  args, pass_through_flags = parser.parse_known_args()
+
+  RunJ2ObjC(args.java,
+            args.jvm_flags,
+            args.j2objc,
+            args.main_class,
+            pass_through_flags)
+  WriteDepMappingFile(args.translated_source_files,
+                      args.objc_file_path,
+                      args.output_dependency_mapping_file)
diff --git a/tools/objc/BUILD b/tools/objc/BUILD
index 2033ce7..7f87137 100644
--- a/tools/objc/BUILD
+++ b/tools/objc/BUILD
@@ -79,9 +79,7 @@
 )
 
 # TODO(bazel-team): Open-source the script once J2ObjC support is open-sourced.
-genrule(
+py_library(
     name = "j2objc_dead_code_pruner",
-    srcs = [],
-    outs = ["dummy_dead_code_pruner.py"],
-    cmd = "touch $(OUTS)",
+    srcs = ["j2objc_dead_code_pruner.py"],
 )
diff --git a/tools/objc/j2objc_dead_code_pruner.py b/tools/objc/j2objc_dead_code_pruner.py
new file mode 100755
index 0000000..546a9ef
--- /dev/null
+++ b/tools/objc/j2objc_dead_code_pruner.py
@@ -0,0 +1,258 @@
+#!/usr/bin/python2.7
+
+# Copyright 2015 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""A script for J2ObjC dead code removal in Blaze.
+
+This script removes unused J2ObjC-translated classes from compilation and
+linking by:
+  1. Build a class dependency tree among translated source files.
+  2. Use user-provided Java class entry points to get a list of reachable
+     classes.
+  3. Go through all translated source files and rewrite unreachable ones with
+     dummy content.
+"""
+
+import argparse
+from collections import OrderedDict
+import multiprocessing
+import os
+import Queue
+import shutil
+import threading
+
+PRUNED_SRC_CONTENT = 'static int DUMMY_unused __attribute__((unused,used)) = 0;'
+
+
+def BuildReachabilityTree(dependency_mapping_files, file_open=open):
+  """Builds a reachability tree using entries from dependency mapping files.
+
+  Args:
+    dependency_mapping_files: A comma separated list of J2ObjC-generated
+        dependency mapping files.
+    file_open: Reference to the builtin open function so it may be
+        overridden for testing.
+  Returns:
+    A dict mapping J2ObjC-generated source files to the corresponding direct
+    dependent source files.
+  """
+  tree = dict()
+  for dependency_mapping_file in dependency_mapping_files.split(','):
+    with file_open(dependency_mapping_file, 'r') as f:
+      for line in f:
+        entry = line.strip().split(':')[0]
+        dep = line.strip().split(':')[1]
+        if entry in tree:
+          tree[entry].append(dep)
+        else:
+          tree[entry] = [dep]
+  return tree
+
+
+def BuildHeaderMapping(header_mapping_files, file_open=open):
+  """Builds a mapping between Java classes and J2ObjC-translated header files.
+
+  Args:
+    header_mapping_files: A comma separated list of J2ObjC-generated
+        header mapping files.
+    file_open: Reference to the builtin open function so it may be
+        overridden for testing.
+  Returns:
+    An ordered dict mapping Java class names to corresponding J2ObjC-translated
+    source files.
+  """
+  header_mapping = OrderedDict()
+  for header_mapping_file in header_mapping_files.split(','):
+    with file_open(header_mapping_file, 'r') as f:
+      for line in f:
+        java_class_name = line.strip().split('=')[0]
+        transpiled_file_name = os.path.splitext(line.strip().split('=')[1])[0]
+        header_mapping[java_class_name] = transpiled_file_name
+  return header_mapping
+
+
+def BuildReachableFileSet(entry_classes, reachability_tree, header_mapping):
+  """Builds a set of reachable translated files from entry Java classes.
+
+  Args:
+    entry_classes: A comma separated list of Java entry classes.
+    reachability_tree: A dict mapping translated files to their direct
+        dependencies.
+    header_mapping: A dict mapping Java class names to translated source files.
+  Returns:
+    A set of reachable translated files from the given list of entry classes.
+  Raises:
+    Exception: If there is an entry class that is not being transpiled in this
+        j2objc_library.
+  """
+  reachable_files = set()
+  for entry_class in entry_classes.split(','):
+    if entry_class not in header_mapping:
+      raise Exception(entry_class +
+                      'is not in the transitive Java deps of included ' +
+                      'j2objc_library rules.')
+    transpiled_file_name = header_mapping[entry_class]
+    reachable_files.add(transpiled_file_name)
+    current_level_deps = reachability_tree[transpiled_file_name]
+    while current_level_deps:
+      next_level_deps = []
+      for dep in current_level_deps:
+        if dep not in reachable_files:
+          reachable_files.add(dep)
+          if dep in reachability_tree:
+            next_level_deps.extend(reachability_tree[dep])
+      current_level_deps = next_level_deps
+  return reachable_files
+
+
+def PruneFiles(input_files, output_files, objc_file_path, reachable_files,
+               file_open=open, file_shutil=shutil):
+  """Copies over translated files and remove the contents of unreachable files.
+
+  Args:
+    input_files: A comma separated list of input source files to prune. It has
+        a one-on-one pair mapping with the output_file list.
+    output_files: A comma separated list of output source files to write pruned
+        source files to. It has a one-on-one pair mapping with the input_file
+        list.
+    objc_file_path: The file path which represents a directory where the
+        generated ObjC files reside.
+    reachable_files: A set of reachable source files.
+    file_open: Reference to the builtin open function so it may be
+        overridden for testing.
+    file_shutil: Reference to the builtin shutil module so it may be
+        overridden for testing.
+  Returns:
+    None.
+  """
+  file_queue = Queue.Queue()
+  for input_file, output_file in zip(input_files.split(','),
+                                     output_files.split(',')):
+    file_queue.put((input_file, output_file))
+
+  for _ in xrange(multiprocessing.cpu_count()):
+    t = threading.Thread(target=_PruneFile, args=(file_queue,
+                                                  reachable_files,
+                                                  objc_file_path,
+                                                  file_open,
+                                                  file_shutil))
+    t.start()
+
+  file_queue.join()
+
+
+def _PruneFile(file_queue, reachable_files, objc_file_path, file_open=open,
+               file_shutil=shutil):
+  while True:
+    try:
+      input_file, output_file = file_queue.get_nowait()
+    except Queue.Empty:
+      return
+    file_name = os.path.relpath(os.path.splitext(input_file)[0],
+                                objc_file_path)
+    # Translated files from package-info.java are also preserved because
+    # they are needed to resolve ObjC class names with prefixes.
+    if file_name in reachable_files or file_name.endswith('package-info'):
+      file_shutil.copy(input_file, output_file)
+    else:
+      f = file_open(output_file, 'w')
+      # Use a static variable scoped to the source file to supress
+      # the "has no symbols" linker warning for empty object files.
+      f.write(PRUNED_SRC_CONTENT)
+      f.close()
+    file_queue.task_done()
+
+
+def PruneDeadCode(input_files, output_files, dependency_mapping_files,
+                  header_mapping_files, entry_classes, objc_file_path,
+                  file_open=open, file_shutil=shutil):
+  """Copies over translated files and remove the contents of unreachable files.
+
+  Args:
+    input_files: A comma separated list of input source files to prune. It has
+        a one-on-one pair mapping with the output_file list.
+    output_files: A comma separated list of output source files to write pruned
+        source files to. It has a one-on-one pair mapping with the input_file
+        list.
+    dependency_mapping_files: A comma separated list of J2ObjC-generated
+        dependency mapping files.
+    header_mapping_files: A comma separated list of J2ObjC-generated
+        header mapping files.
+    entry_classes: A comma separated list of Java entry classes.
+    objc_file_path: The file path which represents a directory where the
+        generated ObjC files reside.
+    file_open: Reference to the builtin open function so it may be
+        overridden for testing.
+    file_shutil: Reference to the builtin shutil module so it may be
+        overridden for testing.
+  Returns:
+    None.
+  """
+  reachability_file_mapping = BuildReachabilityTree(
+      dependency_mapping_files, file_open)
+  header_map = BuildHeaderMapping(header_mapping_files, file_open)
+  reachable_files_set = BuildReachableFileSet(entry_classes,
+                                              reachability_file_mapping,
+                                              header_map)
+  PruneFiles(input_files,
+             output_files,
+             objc_file_path,
+             reachable_files_set,
+             file_open,
+             file_shutil)
+
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '--input_files',
+      required=True,
+      help=('The comma-separated file paths of translated source files to '
+            'prune.'))
+  parser.add_argument(
+      '--output_files',
+      required=True,
+      help='The comma-separated file paths of pruned source files to write to.')
+  parser.add_argument(
+      '--dependency_mapping_files',
+      required=True,
+      help='The comma-separated file paths of dependency mapping files.')
+  parser.add_argument(
+      '--header_mapping_files',
+      required=True,
+      help='The comma-separated file paths of header mapping files.')
+  parser.add_argument(
+      '--entry_classes',
+      required=True,
+      help=('The comma-separated list of Java entry classes to be used as entry'
+            ' point of the dead code anlysis.'))
+  parser.add_argument(
+      '--objc_file_path',
+      required=True,
+      help='The file path which represents a directory where the generated ObjC'
+      ' files reside')
+  args = parser.parse_args()
+
+  if not args.entry_classes:
+    raise Exception('J2objC dead code removal is on but no entry class is ',
+                    'specified in any j2objc_library targets in the transitive',
+                    ' closure')
+  PruneDeadCode(
+      args.input_files,
+      args.output_files,
+      args.dependency_mapping_files,
+      args.header_mapping_files,
+      args.entry_classes,
+      args.objc_file_path)