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)