blob: 090d54f8237c9125d1cc416e45e3d5e4da924cc4 [file] [log] [blame]
Rumou Duan6773c152017-03-24 17:12:25 +00001#!/usr/bin/python2.7
2
3# Copyright 2017 The Bazel Authors. All rights reserved.
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 script to generate Java class to ObjC header mapping for J2ObjC.
18
19This script generates a text file containing mapping between top-level Java
20classes to associated ObjC headers, generated by J2ObjC.
21
22The mapping file is used by dependent J2ObjC transpilation actions to locate
23the correct header import paths for dependent Java classes.
24
25Inside the script, we read the Java source files and source jars of a single
26Java rule, and parse out the package names from the package statements, using
27regular expression matching.
28
29Note that we cannot guarantee 100% correctness by using just regular expression,
30but it should be good enough. This allows us to avoid doing any further complex
31parsing of the source files and keep the script light-weight without other
32dependencies. In the future, we can consider implementing a simple Java lexer
33here that correctly parses the package statements out of Java files.
34"""
35
36import argparse
37import os
38import re
39import zipfile
40
41_PACKAGE_RE = re.compile(r'(package)\s+([\w\.]+);')
42
43
44def _get_file_map_entry(java_file_path, java_file):
45 """Returns the top-level Java class and header file path tuple.
46
47 Args:
48 java_file_path: The file path of the source Java file.
49 java_file: The actual file of the source java file.
50 Returns:
51 A tuple containing top-level Java class and associated header file path. Or
52 None if no package statement exists in the source file.
53 """
54 for line in java_file:
55 stripped_line = line.strip()
56 package_statement = _PACKAGE_RE.search(stripped_line)
57
58 # We identified a potential package statement.
59 if package_statement:
60 preceding_characters = stripped_line[0:package_statement.start(1)]
61 # We have preceding characters before the package statement. We need to
62 # look further into them.
63 if preceding_characters:
64 # Skip comment line.
65 if preceding_characters.startswith('//'):
66 continue
67
68 # Preceding characters also must end with a space, represent an end
69 # of comment, or end of a statement.
70 # Otherwise, we skip the current line.
71 if not (preceding_characters[len(preceding_characters) - 1].isspace() or
72 preceding_characters.endswith(';') or
73 preceding_characters.endswith('*/')):
74 continue
75 package_name = package_statement.group(2)
76 class_name = os.path.splitext(os.path.basename(java_file_path))[0]
77 header_file = os.path.splitext(java_file_path)[0] + '.h'
78 return (package_name + '.' + class_name, header_file)
79 return None
80
81
82def main():
83 parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
84 parser.add_argument(
85 '--source_files',
86 required=False,
87 help='The source files')
88 parser.add_argument(
89 '--source_jars',
90 required=False,
91 help='The source jars.')
92 parser.add_argument(
93 '--output_mapping_file',
94 required=False,
95 help='The output mapping file')
96
97 args, _ = parser.parse_known_args()
98 class_to_header_map = dict()
99
100 # Process the source files.
101 if args.source_files:
102 source_files = args.source_files.split(',')
103 for source_file in source_files:
104 with open(source_file, 'r') as f:
105 entry = _get_file_map_entry(source_file, f)
106 if entry:
107 class_to_header_map[entry[0]] = entry[1]
108
109 # Process the source jars.
110 if args.source_jars:
111 source_jars = args.source_jars.split(',')
112 for source_jar in source_jars:
113 with zipfile.ZipFile(source_jar, 'r') as jar:
114 for jar_entry in jar.namelist():
115 if jar_entry.endswith('.java'):
116 with jar.open(jar_entry) as jar_entry_file:
117 entry = _get_file_map_entry(jar_entry, jar_entry_file)
118 if entry:
119 class_to_header_map[entry[0]] = entry[1]
120
121 # Generate the output header mapping file.
122 if args.output_mapping_file:
123 with open(args.output_mapping_file, 'w') as output_mapping_file:
124 for class_name in sorted(class_to_header_map):
125 header_path = class_to_header_map[class_name]
126 output_mapping_file.write(class_name + '=' + header_path + '\n')
127
128if __name__ == '__main__':
129 main()