Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 1 | #!/usr/bin/python2.7 |
| 2 | |
Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 3 | # Copyright 2015 The Bazel Authors. All rights reserved. |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 4 | # |
| 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 | |
| 19 | This script wraps around J2ObjC transpiler to also output a dependency mapping |
| 20 | file by scanning the import and include directives of the J2ObjC-translated |
| 21 | files. |
| 22 | """ |
| 23 | |
| 24 | import argparse |
| 25 | import multiprocessing |
| 26 | import os |
| 27 | import Queue |
| 28 | import re |
| 29 | import subprocess |
Rumou Duan | 18742ad | 2015-09-02 23:45:03 +0000 | [diff] [blame] | 30 | import tempfile |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 31 | import threading |
| 32 | |
| 33 | INCLUDE_RE = re.compile('#(include|import) "([^"]+)"') |
| 34 | |
| 35 | |
Rumou Duan | 18742ad | 2015-09-02 23:45:03 +0000 | [diff] [blame] | 36 | def RunJ2ObjC(java, jvm_flags, j2objc, main_class, j2objc_args): |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 37 | """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 Duan | 18742ad | 2015-09-02 23:45:03 +0000 | [diff] [blame] | 44 | j2objc_args: A list of args to pass to J2ObjC transpiler. |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 45 | Returns: |
| 46 | None. |
| 47 | """ |
Rumou Duan | 18742ad | 2015-09-02 23:45:03 +0000 | [diff] [blame] | 48 | 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 Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 68 | |
| 69 | |
| 70 | def 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 Duan | 49cdb4b | 2015-12-04 18:03:44 +0000 | [diff] [blame] | 88 | Raises: |
| 89 | RuntimeError: If spawned threads throw errors during processing. |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 90 | Returns: |
| 91 | None. |
| 92 | """ |
| 93 | dep_mapping = dict() |
| 94 | input_file_queue = Queue.Queue() |
| 95 | output_dep_mapping_queue = Queue.Queue() |
Rumou Duan | 49cdb4b | 2015-12-04 18:03:44 +0000 | [diff] [blame] | 96 | error_message_queue = Queue.Queue() |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 97 | 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 Duan | 49cdb4b | 2015-12-04 18:03:44 +0000 | [diff] [blame] | 103 | error_message_queue, |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 104 | objc_file_path, |
| 105 | file_open)) |
| 106 | t.start() |
| 107 | |
| 108 | input_file_queue.join() |
| 109 | |
Rumou Duan | 49cdb4b | 2015-12-04 18:03:44 +0000 | [diff] [blame] | 110 | 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 Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 115 | 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 Duan | 49cdb4b | 2015-12-04 18:03:44 +0000 | [diff] [blame] | 126 | def _ReadDepMapping(input_file_queue, output_dep_mapping_queue, |
| 127 | error_message_queue, objc_file_path, file_open=open): |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 128 | while True: |
| 129 | try: |
| 130 | input_file = input_file_queue.get_nowait() |
| 131 | except Queue.Empty: |
Rumou Duan | 49cdb4b | 2015-12-04 18:03:44 +0000 | [diff] [blame] | 132 | # No more work left in the queue. |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 133 | return |
Rumou Duan | 49cdb4b | 2015-12-04 18:03:44 +0000 | [diff] [blame] | 134 | |
| 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 Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 153 | |
Rumou Duan | 18742ad | 2015-09-02 23:45:03 +0000 | [diff] [blame] | 154 | |
Rumou Duan | 123e1c3 | 2016-02-01 16:16:15 +0000 | [diff] [blame] | 155 | def 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 Duan | 18742ad | 2015-09-02 23:45:03 +0000 | [diff] [blame] | 180 | def _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 Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 203 | if __name__ == '__main__': |
Rumou Duan | ab16dd6 | 2015-08-18 21:52:08 +0000 | [diff] [blame] | 204 | parser = argparse.ArgumentParser(fromfile_prefix_chars='@') |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 205 | 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 Duan | 123e1c3 | 2016-02-01 16:16:15 +0000 | [diff] [blame] | 235 | 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 Duan | 18742ad | 2015-09-02 23:45:03 +0000 | [diff] [blame] | 246 | args, pass_through_args = parser.parse_known_args() |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 247 | |
| 248 | RunJ2ObjC(args.java, |
| 249 | args.jvm_flags, |
| 250 | args.j2objc, |
| 251 | args.main_class, |
Rumou Duan | 18742ad | 2015-09-02 23:45:03 +0000 | [diff] [blame] | 252 | pass_through_args) |
Michael Thvedt | 828a4be | 2015-08-12 17:45:36 +0000 | [diff] [blame] | 253 | WriteDepMappingFile(args.translated_source_files, |
| 254 | args.objc_file_path, |
| 255 | args.output_dependency_mapping_file) |
Rumou Duan | 123e1c3 | 2016-02-01 16:16:15 +0000 | [diff] [blame] | 256 | |
| 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) |