OLD | NEW |
| (Empty) |
1 # Copyright 2017 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 import optparse | |
6 import os.path | |
7 import re | |
8 import sys | |
9 | |
10 # Path handling for libraries and templates | |
11 # Paths have to be normalized because Jinja uses the exact template path to | |
12 # determine the hash used in the cache filename, and we need a pre-caching step | |
13 # to be concurrency-safe. Use absolute path because __file__ is absolute if | |
14 # module is imported, and relative if executed directly. | |
15 # If paths differ between pre-caching and individual file compilation, the cache | |
16 # is regenerated, which causes a race condition and breaks concurrent build, | |
17 # since some compile processes will try to read the partially written cache. | |
18 module_path, module_filename = os.path.split(os.path.realpath(__file__)) | |
19 templates_dir = module_path | |
20 third_party_dir = os.path.normpath(os.path.join( | |
21 module_path, os.pardir, os.pardir, os.pardir, os.pardir)) | |
22 # jinja2 is in chromium's third_party directory. | |
23 # Insert at 1 so at front to override system libraries, and | |
24 # after path[0] == invoking script dir | |
25 sys.path.insert(1, third_party_dir) | |
26 import jinja2 | |
27 | |
28 | |
29 def to_lower_case(name): | |
30 return name[:1].lower() + name[1:] | |
31 | |
32 | |
33 def agent_name_to_class(agent_name): | |
34 if agent_name == "Performance": | |
35 return "PerformanceMonitor" | |
36 elif agent_name == "TraceEvents": | |
37 return "InspectorTraceEvents" | |
38 else: | |
39 return "Inspector%sAgent" % agent_name | |
40 | |
41 | |
42 def initialize_jinja_env(cache_dir): | |
43 jinja_env = jinja2.Environment( | |
44 loader=jinja2.FileSystemLoader(templates_dir), | |
45 # Bytecode cache is not concurrency-safe unless pre-cached: | |
46 # if pre-cached this is read-only, but writing creates a race condition. | |
47 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), | |
48 keep_trailing_newline=True, # newline-terminate generated files | |
49 lstrip_blocks=True, # so can indent control flow tags | |
50 trim_blocks=True) | |
51 jinja_env.filters.update({ | |
52 "to_lower_case": to_lower_case, | |
53 "agent_name_to_class": agent_name_to_class}) | |
54 jinja_env.add_extension('jinja2.ext.loopcontrols') | |
55 return jinja_env | |
56 | |
57 | |
58 def match_and_consume(pattern, source): | |
59 match = re.match(pattern, source) | |
60 if match: | |
61 return match, source[len(match.group(0)):].strip() | |
62 return None, source | |
63 | |
64 | |
65 def load_model_from_idl(source): | |
66 source = re.sub(r"//.*", "", source) # Remove line comments | |
67 source = re.sub(r"/\*(.|\n)*?\*/", "", source, re.MULTILINE) # Remove block
comments | |
68 source = re.sub(r"\]\s*?\n\s*", "] ", source) # Merge the method annotation
with the next line | |
69 source = source.strip() | |
70 model = [] | |
71 while len(source): | |
72 match, source = match_and_consume(r"interface\s(\w*)\s?\{([^\{]*)\}", so
urce) | |
73 if not match: | |
74 sys.stderr.write("Cannot parse %s\n" % source[:100]) | |
75 sys.exit(1) | |
76 model.append(File(match.group(1), match.group(2))) | |
77 return model | |
78 | |
79 | |
80 class File(object): | |
81 def __init__(self, name, source): | |
82 self.name = name | |
83 self.header_name = self.name + "Inl" | |
84 self.includes = [include_inspector_header("InspectorInstrumentation")] | |
85 self.forward_declarations = [] | |
86 self.declarations = [] | |
87 for line in map(str.strip, source.split("\n")): | |
88 line = re.sub(r"\s{2,}", " ", line).strip() # Collapse whitespace | |
89 if len(line) == 0: | |
90 continue | |
91 if line[0] == "#": | |
92 self.includes.append(line) | |
93 elif line.startswith("class "): | |
94 self.forward_declarations.append(line) | |
95 else: | |
96 self.declarations.append(Method(line)) | |
97 self.includes.sort() | |
98 self.forward_declarations.sort() | |
99 | |
100 | |
101 def include_header(name): | |
102 return "#include \"%s.h\"" % name | |
103 | |
104 | |
105 def include_inspector_header(name): | |
106 if name == "PerformanceMonitor": | |
107 return include_header("core/frame/" + name) | |
108 return include_header("core/inspector/" + name) | |
109 | |
110 | |
111 class Method(object): | |
112 def __init__(self, source): | |
113 match = re.match(r"(\[[\w|,|=|\s]*\])?\s?(\w*\*?) (\w*)\((.*)\)\s?;", so
urce) | |
114 if not match: | |
115 sys.stderr.write("Cannot parse %s\n" % source) | |
116 sys.exit(1) | |
117 | |
118 self.options = [] | |
119 if match.group(1): | |
120 options_str = re.sub(r"\s", "", match.group(1)[1:-1]) | |
121 if len(options_str) != 0: | |
122 self.options = options_str.split(",") | |
123 | |
124 self.return_type = match.group(2) | |
125 self.name = match.group(3) | |
126 self.is_scoped = self.return_type == "" | |
127 | |
128 # Splitting parameters by a comma, assuming that attribute lists contain
no more than one attribute. | |
129 self.params = map(Parameter, map(str.strip, match.group(4).split(","))) | |
130 | |
131 self.returns_value = self.return_type != "" and self.return_type != "voi
d" | |
132 if self.return_type == "bool": | |
133 self.default_return_value = "false" | |
134 elif self.returns_value: | |
135 sys.stderr.write("Can only return bool: %s\n" % self.name) | |
136 sys.exit(1) | |
137 | |
138 self.agents = [option for option in self.options if "=" not in option] | |
139 | |
140 if self.returns_value and len(self.agents) > 1: | |
141 sys.stderr.write("Can only return value from a single agent: %s\n" %
self.name) | |
142 sys.exit(1) | |
143 | |
144 | |
145 class Parameter(object): | |
146 def __init__(self, source): | |
147 self.options = [] | |
148 match, source = match_and_consume(r"\[(\w*)\]", source) | |
149 if match: | |
150 self.options.append(match.group(1)) | |
151 | |
152 parts = map(str.strip, source.split("=")) | |
153 self.default_value = parts[1] if len(parts) != 1 else None | |
154 | |
155 param_decl = parts[0] | |
156 min_type_tokens = 2 if re.match("(const|unsigned long) ", param_decl) el
se 1 | |
157 | |
158 if len(param_decl.split(" ")) > min_type_tokens: | |
159 parts = param_decl.split(" ") | |
160 self.type = " ".join(parts[:-1]) | |
161 self.name = parts[-1] | |
162 else: | |
163 self.type = param_decl | |
164 self.name = build_param_name(self.type) | |
165 | |
166 self.value = self.name | |
167 self.is_prp = re.match(r"PassRefPtr<", param_decl) is not None | |
168 if self.is_prp: | |
169 self.name = "prp" + self.name[0].upper() + self.name[1:] | |
170 self.inner_type = re.match(r"PassRefPtr<(.+)>", param_decl).group(1) | |
171 | |
172 if self.type[-1] == "*" and "char" not in self.type: | |
173 self.member_type = "Member<%s>" % self.type[:-1] | |
174 else: | |
175 self.member_type = self.type | |
176 | |
177 | |
178 def build_param_name(param_type): | |
179 base_name = re.match(r"(const |PassRefPtr<)?(\w*)", param_type).group(2) | |
180 return "param" + base_name | |
181 | |
182 | |
183 cmdline_parser = optparse.OptionParser() | |
184 cmdline_parser.add_option("--output_dir") | |
185 cmdline_parser.add_option("--template_dir") | |
186 | |
187 try: | |
188 arg_options, arg_values = cmdline_parser.parse_args() | |
189 if len(arg_values) != 1: | |
190 raise Exception("Exactly one plain argument expected (found %s)" % len(a
rg_values)) | |
191 input_path = arg_values[0] | |
192 output_dirpath = arg_options.output_dir | |
193 if not output_dirpath: | |
194 raise Exception("Output directory must be specified") | |
195 except Exception: | |
196 # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html | |
197 exc = sys.exc_info()[1] | |
198 sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc) | |
199 sys.stderr.write("Usage: <script> --output_dir <output_dir> InstrumentingPro
bes.idl\n") | |
200 exit(1) | |
201 | |
202 jinja_env = initialize_jinja_env(output_dirpath) | |
203 fin = open(input_path, "r") | |
204 files = load_model_from_idl(fin.read()) | |
205 fin.close() | |
206 all_agents = set() | |
207 | |
208 for f in files: | |
209 for declaration in f.declarations: | |
210 for agent in declaration.agents: | |
211 all_agents.add(agent) | |
212 | |
213 template_context = { | |
214 "files": files, | |
215 "agents": all_agents, | |
216 "input_file": os.path.basename(input_path) | |
217 } | |
218 cpp_template = jinja_env.get_template("/InstrumentingProbesImpl_cpp.template") | |
219 cpp_file = open(output_dirpath + "/InstrumentingProbesImpl.cpp", "w") | |
220 cpp_file.write(cpp_template.render(template_context)) | |
221 cpp_file.close() | |
222 | |
223 agents_h_template = jinja_env.get_template("/InstrumentingAgents_h.template") | |
224 agents_h_file = open(output_dirpath + "/InstrumentingAgents.h", "w") | |
225 agents_h_file.write(agents_h_template.render(template_context)) | |
226 agents_h_file.close() | |
227 | |
228 for f in files: | |
229 template_context["file"] = f | |
230 h_template = jinja_env.get_template("/InstrumentingProbesImpl_h.template") | |
231 h_file = open(output_dirpath + "/" + f.name + "Inl.h", "w") | |
232 h_file.write(h_template.render(template_context)) | |
233 h_file.close() | |
OLD | NEW |