blob: 6ae6d5b73fe4185216881483366ac5c2890dcc96 [file] [log] [blame]
Michael Thvedt828a4be2015-08-12 17:45:36 +00001#!/usr/bin/python2.7
2
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00003# Copyright 2015 The Bazel Authors. All rights reserved.
Michael Thvedt828a4be2015-08-12 17:45:36 +00004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http:#www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""A wrapper script for J2ObjC transpiler.
18
19This script wraps around J2ObjC transpiler to also output a dependency mapping
20file by scanning the import and include directives of the J2ObjC-translated
21files.
22"""
23
24import argparse
25import multiprocessing
26import os
27import Queue
28import re
29import subprocess
Rumou Duan18742ad2015-09-02 23:45:03 +000030import tempfile
Michael Thvedt828a4be2015-08-12 17:45:36 +000031import threading
32
33INCLUDE_RE = re.compile('#(include|import) "([^"]+)"')
34
35
Rumou Duan18742ad2015-09-02 23:45:03 +000036def RunJ2ObjC(java, jvm_flags, j2objc, main_class, j2objc_args):
Michael Thvedt828a4be2015-08-12 17:45:36 +000037 """Runs J2ObjC transpiler to translate Java source files to ObjC.
38
39 Args:
40 java: The path of the Java executable.
41 jvm_flags: A comma-separated list of flags to pass to JVM.
42 j2objc: The deploy jar of J2ObjC.
43 main_class: The J2ObjC main class to invoke.
Rumou Duan18742ad2015-09-02 23:45:03 +000044 j2objc_args: A list of args to pass to J2ObjC transpiler.
Michael Thvedt828a4be2015-08-12 17:45:36 +000045 Returns:
46 None.
47 """
Rumou Duan18742ad2015-09-02 23:45:03 +000048 source_files, flags = _ParseArgs(j2objc_args)
49 source_file_manifest_content = ' '.join(source_files)
50 fd = None
51 param_filename = None
52 try:
53 fd, param_filename = tempfile.mkstemp(text=True)
54 os.write(fd, source_file_manifest_content)
55 finally:
56 if fd:
57 os.close(fd)
58 try:
59 j2objc_cmd = [java]
60 j2objc_cmd.extend(filter(None, jvm_flags.split(',')))
61 j2objc_cmd.extend(['-cp', j2objc, main_class])
62 j2objc_cmd.extend(flags)
63 j2objc_cmd.extend(['@%s' % param_filename])
64 subprocess.check_call(j2objc_cmd, stderr=subprocess.STDOUT)
65 finally:
66 if param_filename:
67 os.remove(param_filename)
Michael Thvedt828a4be2015-08-12 17:45:36 +000068
69
70def WriteDepMappingFile(translated_source_files,
71 objc_file_path,
72 output_dependency_mapping_file,
73 file_open=open):
74 """Scans J2ObjC-translated files and outputs a dependency mapping file.
75
76 The mapping file contains mappings between translated source files and their
77 imported source files scanned from the import and include directives.
78
79 Args:
80 translated_source_files: A comma-separated list of files translated by
81 J2ObjC.
82 objc_file_path: The file path which represents a directory where the
83 generated ObjC files reside.
84 output_dependency_mapping_file: The path of the dependency mapping file to
85 write to.
86 file_open: Reference to the builtin open function so it may be
87 overridden for testing.
Rumou Duan49cdb4b2015-12-04 18:03:44 +000088 Raises:
89 RuntimeError: If spawned threads throw errors during processing.
Michael Thvedt828a4be2015-08-12 17:45:36 +000090 Returns:
91 None.
92 """
93 dep_mapping = dict()
94 input_file_queue = Queue.Queue()
95 output_dep_mapping_queue = Queue.Queue()
Rumou Duan49cdb4b2015-12-04 18:03:44 +000096 error_message_queue = Queue.Queue()
Michael Thvedt828a4be2015-08-12 17:45:36 +000097 for output_file in translated_source_files.split(','):
98 input_file_queue.put(output_file)
99
100 for _ in xrange(multiprocessing.cpu_count()):
101 t = threading.Thread(target=_ReadDepMapping, args=(input_file_queue,
102 output_dep_mapping_queue,
Rumou Duan49cdb4b2015-12-04 18:03:44 +0000103 error_message_queue,
Michael Thvedt828a4be2015-08-12 17:45:36 +0000104 objc_file_path,
105 file_open))
106 t.start()
107
108 input_file_queue.join()
109
Rumou Duan49cdb4b2015-12-04 18:03:44 +0000110 if not error_message_queue.empty():
111 error_messages = [error_message for error_message in
112 error_message_queue.queue]
113 raise RuntimeError('\n'.join(error_messages))
114
Michael Thvedt828a4be2015-08-12 17:45:36 +0000115 while not output_dep_mapping_queue.empty():
116 entry_file, deps = output_dep_mapping_queue.get()
117 dep_mapping[entry_file] = deps
118
119 f = file_open(output_dependency_mapping_file, 'w')
120 for entry in sorted(dep_mapping):
121 for dep in dep_mapping[entry]:
122 f.write(entry + ':' + dep + '\n')
123 f.close()
124
125
Rumou Duan49cdb4b2015-12-04 18:03:44 +0000126def _ReadDepMapping(input_file_queue, output_dep_mapping_queue,
127 error_message_queue, objc_file_path, file_open=open):
Michael Thvedt828a4be2015-08-12 17:45:36 +0000128 while True:
129 try:
130 input_file = input_file_queue.get_nowait()
131 except Queue.Empty:
Rumou Duan49cdb4b2015-12-04 18:03:44 +0000132 # No more work left in the queue.
Michael Thvedt828a4be2015-08-12 17:45:36 +0000133 return
Rumou Duan49cdb4b2015-12-04 18:03:44 +0000134
135 try:
136 deps = []
137 entry = os.path.relpath(os.path.splitext(input_file)[0], objc_file_path)
138 with file_open(input_file, 'r') as f:
139 for line in f:
140 include = INCLUDE_RE.match(line)
141 if include:
142 include_path = include.group(2)
143 dep = os.path.splitext(include_path)[0]
144 if dep != entry:
145 deps.append(dep)
146 output_dep_mapping_queue.put((entry, deps))
147 except Exception as e: # pylint: disable=broad-except
148 error_message_queue.put(str(e))
149 finally:
150 # We need to mark the task done to prevent blocking the main process
151 # indefinitely.
152 input_file_queue.task_done()
Michael Thvedt828a4be2015-08-12 17:45:36 +0000153
Rumou Duan18742ad2015-09-02 23:45:03 +0000154
Rumou Duan123e1c32016-02-01 16:16:15 +0000155def WriteArchiveSourceMappingFile(compiled_archive_file_path,
156 output_archive_source_mapping_file,
157 translated_source_files,
158 objc_file_path,
159 file_open=open):
160 """Writes a mapping file between archive file to associated ObjC source files.
161
162 Args:
163 compiled_archive_file_path: The path of the archive file.
164 output_archive_source_mapping_file: A path of the mapping file to write to.
165 translated_source_files: A comma-separated list of source files translated
166 by J2ObjC.
167 objc_file_path: The file path which represents a directory where the
168 generated ObjC files reside.
169 file_open: Reference to the builtin open function so it may be
170 overridden for testing.
171 Returns:
172 None.
173 """
174 with file_open(output_archive_source_mapping_file, 'w') as f:
175 for translated_source_file in translated_source_files.split(','):
176 file_path = os.path.relpath(translated_source_file, objc_file_path)
177 f.write(compiled_archive_file_path + ':' + file_path + '\n')
178
179
Rumou Duan18742ad2015-09-02 23:45:03 +0000180def _ParseArgs(j2objc_args):
181 """Separate arguments passed to J2ObjC into source files and J2ObjC flags.
182
183 Args:
184 j2objc_args: A list of args to pass to J2ObjC transpiler.
185 Returns:
186 A tuple containing source files and J2ObjC flags
187 """
188 source_files = []
189 flags = []
190 is_next_flag_value = False
191 for j2objc_arg in j2objc_args:
192 if j2objc_arg.startswith('-'):
193 flags.append(j2objc_arg)
194 is_next_flag_value = True
195 elif is_next_flag_value:
196 flags.append(j2objc_arg)
197 is_next_flag_value = False
198 else:
199 source_files.append(j2objc_arg)
200 return (source_files, flags)
201
202
Michael Thvedt828a4be2015-08-12 17:45:36 +0000203if __name__ == '__main__':
Rumou Duanab16dd62015-08-18 21:52:08 +0000204 parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
Michael Thvedt828a4be2015-08-12 17:45:36 +0000205 parser.add_argument(
206 '--java',
207 required=True,
208 help='The path to the Java executable.')
209 parser.add_argument(
210 '--jvm_flags',
211 default='',
212 help='A comma-separated list of flags to pass to the JVM.')
213 parser.add_argument(
214 '--j2objc',
215 required=True,
216 help='The path to the J2ObjC deploy jar.')
217 parser.add_argument(
218 '--main_class',
219 required=True,
220 help='The main class of the J2ObjC deploy jar to execute.')
221 parser.add_argument(
222 '--translated_source_files',
223 required=True,
224 help=('A comma-separated list of file paths where J2ObjC will write the '
225 'translated files to.'))
226 parser.add_argument(
227 '--output_dependency_mapping_file',
228 required=True,
229 help='The file path of the dependency mapping file to write to.')
230 parser.add_argument(
231 '--objc_file_path',
232 required=True,
233 help=('The file path which represents a directory where the generated '
234 'ObjC files reside.'))
Rumou Duan123e1c32016-02-01 16:16:15 +0000235 parser.add_argument(
236 '--output_archive_source_mapping_file',
237 help='The file path of the mapping file containing mappings between the '
238 'translated source files and the to-be-generated archive file '
239 'compiled from those source files. --compile_archive_file_path must '
240 'be specified if this option is specified.')
241 parser.add_argument(
242 '--compiled_archive_file_path',
243 required=False,
244 help=('The archive file path that will be produced by ObjC compile action'
245 ' later'))
Rumou Duan18742ad2015-09-02 23:45:03 +0000246 args, pass_through_args = parser.parse_known_args()
Michael Thvedt828a4be2015-08-12 17:45:36 +0000247
248 RunJ2ObjC(args.java,
249 args.jvm_flags,
250 args.j2objc,
251 args.main_class,
Rumou Duan18742ad2015-09-02 23:45:03 +0000252 pass_through_args)
Michael Thvedt828a4be2015-08-12 17:45:36 +0000253 WriteDepMappingFile(args.translated_source_files,
254 args.objc_file_path,
255 args.output_dependency_mapping_file)
Rumou Duan123e1c32016-02-01 16:16:15 +0000256
257 if args.output_archive_source_mapping_file:
258 WriteArchiveSourceMappingFile(args.compiled_archive_file_path,
259 args.output_archive_source_mapping_file,
260 args.translated_source_files,
261 args.objc_file_path)