blob: 6ae6d5b73fe4185216881483366ac5c2890dcc96 [file] [log] [blame]
#!/usr/bin/python2.7
# Copyright 2015 The Bazel Authors. 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 tempfile
import threading
INCLUDE_RE = re.compile('#(include|import) "([^"]+)"')
def RunJ2ObjC(java, jvm_flags, j2objc, main_class, j2objc_args):
"""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.
j2objc_args: A list of args to pass to J2ObjC transpiler.
Returns:
None.
"""
source_files, flags = _ParseArgs(j2objc_args)
source_file_manifest_content = ' '.join(source_files)
fd = None
param_filename = None
try:
fd, param_filename = tempfile.mkstemp(text=True)
os.write(fd, source_file_manifest_content)
finally:
if fd:
os.close(fd)
try:
j2objc_cmd = [java]
j2objc_cmd.extend(filter(None, jvm_flags.split(',')))
j2objc_cmd.extend(['-cp', j2objc, main_class])
j2objc_cmd.extend(flags)
j2objc_cmd.extend(['@%s' % param_filename])
subprocess.check_call(j2objc_cmd, stderr=subprocess.STDOUT)
finally:
if param_filename:
os.remove(param_filename)
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.
Raises:
RuntimeError: If spawned threads throw errors during processing.
Returns:
None.
"""
dep_mapping = dict()
input_file_queue = Queue.Queue()
output_dep_mapping_queue = Queue.Queue()
error_message_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,
error_message_queue,
objc_file_path,
file_open))
t.start()
input_file_queue.join()
if not error_message_queue.empty():
error_messages = [error_message for error_message in
error_message_queue.queue]
raise RuntimeError('\n'.join(error_messages))
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,
error_message_queue, objc_file_path, file_open=open):
while True:
try:
input_file = input_file_queue.get_nowait()
except Queue.Empty:
# No more work left in the queue.
return
try:
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))
except Exception as e: # pylint: disable=broad-except
error_message_queue.put(str(e))
finally:
# We need to mark the task done to prevent blocking the main process
# indefinitely.
input_file_queue.task_done()
def WriteArchiveSourceMappingFile(compiled_archive_file_path,
output_archive_source_mapping_file,
translated_source_files,
objc_file_path,
file_open=open):
"""Writes a mapping file between archive file to associated ObjC source files.
Args:
compiled_archive_file_path: The path of the archive file.
output_archive_source_mapping_file: A path of the mapping file to write to.
translated_source_files: A comma-separated list of source files translated
by J2ObjC.
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.
Returns:
None.
"""
with file_open(output_archive_source_mapping_file, 'w') as f:
for translated_source_file in translated_source_files.split(','):
file_path = os.path.relpath(translated_source_file, objc_file_path)
f.write(compiled_archive_file_path + ':' + file_path + '\n')
def _ParseArgs(j2objc_args):
"""Separate arguments passed to J2ObjC into source files and J2ObjC flags.
Args:
j2objc_args: A list of args to pass to J2ObjC transpiler.
Returns:
A tuple containing source files and J2ObjC flags
"""
source_files = []
flags = []
is_next_flag_value = False
for j2objc_arg in j2objc_args:
if j2objc_arg.startswith('-'):
flags.append(j2objc_arg)
is_next_flag_value = True
elif is_next_flag_value:
flags.append(j2objc_arg)
is_next_flag_value = False
else:
source_files.append(j2objc_arg)
return (source_files, flags)
if __name__ == '__main__':
parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
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.'))
parser.add_argument(
'--output_archive_source_mapping_file',
help='The file path of the mapping file containing mappings between the '
'translated source files and the to-be-generated archive file '
'compiled from those source files. --compile_archive_file_path must '
'be specified if this option is specified.')
parser.add_argument(
'--compiled_archive_file_path',
required=False,
help=('The archive file path that will be produced by ObjC compile action'
' later'))
args, pass_through_args = parser.parse_known_args()
RunJ2ObjC(args.java,
args.jvm_flags,
args.j2objc,
args.main_class,
pass_through_args)
WriteDepMappingFile(args.translated_source_files,
args.objc_file_path,
args.output_dependency_mapping_file)
if args.output_archive_source_mapping_file:
WriteArchiveSourceMappingFile(args.compiled_archive_file_path,
args.output_archive_source_mapping_file,
args.translated_source_files,
args.objc_file_path)