Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(11)

Side by Side Diff: third_party/WebKit/Source/platform/probe/InstrumentingProbesCodeGenerator.py

Issue 2790973002: [instrumentation] Introduce JSON5 config files for probe generator. (Closed)
Patch Set: add missing config files Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 = os.path.join(module_path, "templates")
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_singular(text):
30 return text[:-1] if text[-1] == "s" else text
31
32
33 def to_lower_case(name):
34 return name[:1].lower() + name[1:]
35
36
37 def agent_name_to_class(agent_name):
38 if agent_name == "Performance":
39 return "PerformanceMonitor"
40 elif agent_name == "TraceEvents":
41 return "InspectorTraceEvents"
42 elif agent_name == "PlatformTraceEvents":
43 return "PlatformTraceEventsAgent"
44 else:
45 return "Inspector%sAgent" % agent_name
46
47
48 def initialize_jinja_env(cache_dir):
49 jinja_env = jinja2.Environment(
50 loader=jinja2.FileSystemLoader(templates_dir),
51 # Bytecode cache is not concurrency-safe unless pre-cached:
52 # if pre-cached this is read-only, but writing creates a race condition.
53 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir),
54 keep_trailing_newline=True, # newline-terminate generated files
55 lstrip_blocks=True, # so can indent control flow tags
56 trim_blocks=True)
57 jinja_env.filters.update({
58 "to_lower_case": to_lower_case,
59 "to_singular": to_singular,
60 "agent_name_to_class": agent_name_to_class})
61 jinja_env.add_extension('jinja2.ext.loopcontrols')
62 return jinja_env
63
64
65 def match_and_consume(pattern, source):
66 match = re.match(pattern, source)
67 if match:
68 return match, source[len(match.group(0)):].strip()
69 return None, source
70
71
72 def load_model_from_idl(source):
73 source = re.sub(r"//.*", "", source) # Remove line comments
74 source = re.sub(r"/\*(.|\n)*?\*/", "", source, re.MULTILINE) # Remove block comments
75 source = re.sub(r"\]\s*?\n\s*", "] ", source) # Merge the method annotation with the next line
76 source = source.strip()
77 model = []
78 while len(source):
79 match, source = match_and_consume(r"interface\s(\w*)\s?\{([^\{]*)\}", so urce)
80 if not match:
81 sys.stderr.write("Cannot parse %s\n" % source[:100])
82 sys.exit(1)
83 model.append(File(match.group(1), match.group(2)))
84 return model
85
86
87 class File(object):
88 def __init__(self, name, source):
89 self.name = name
90 self.header_name = self.name + "Inl"
91 self.includes = [include_inspector_header(base_name)]
92 self.forward_declarations = []
93 self.declarations = []
94 self.defines = []
95 for line in map(str.strip, source.split("\n")):
96 line = re.sub(r"\s{2,}", " ", line).strip() # Collapse whitespace
97 if len(line) == 0:
98 continue
99 if line.startswith("#define"):
100 self.defines.append(line)
101 elif line.startswith("#include"):
102 self.includes.append(line)
103 elif line.startswith("class ") or line.startswith("struct "):
104 self.forward_declarations.append(line)
105 else:
106 self.declarations.append(Method(line))
107 self.includes.sort()
108 self.forward_declarations.sort()
109
110
111 def include_header(name):
112 return "#include \"%s.h\"" % name
113
114
115 def include_inspector_header(name):
116 if name == "PerformanceMonitor":
117 return include_header("core/frame/" + name)
118 if name == "PlatformProbes":
119 return include_header("platform/probe/" + name)
120 if name == "CoreProbes":
121 return include_header("core/probe/" + name)
122 return include_header("core/inspector/" + name)
123
124
125 class Method(object):
126 def __init__(self, source):
127 match = re.match(r"(\[[\w|,|=|\s]*\])?\s?(\w*\*?) (\w*)\((.*)\)\s?;", so urce)
128 if not match:
129 sys.stderr.write("Cannot parse %s\n" % source)
130 sys.exit(1)
131
132 self.options = []
133 if match.group(1):
134 options_str = re.sub(r"\s", "", match.group(1)[1:-1])
135 if len(options_str) != 0:
136 self.options = options_str.split(",")
137
138 self.return_type = match.group(2)
139 self.name = match.group(3)
140 self.is_scoped = self.return_type == ""
141
142 # Splitting parameters by a comma, assuming that attribute lists contain no more than one attribute.
143 self.params = map(Parameter, map(str.strip, match.group(4).split(",")))
144
145 self.returns_value = self.return_type != "" and self.return_type != "voi d"
146 if self.return_type == "bool":
147 self.default_return_value = "false"
148 elif self.returns_value:
149 sys.stderr.write("Can only return bool: %s\n" % self.name)
150 sys.exit(1)
151
152 self.agents = [option for option in self.options if "=" not in option]
153
154 if self.returns_value and len(self.agents) > 1:
155 sys.stderr.write("Can only return value from a single agent: %s\n" % self.name)
156 sys.exit(1)
157
158
159 class Parameter(object):
160 def __init__(self, source):
161 self.options = []
162 match, source = match_and_consume(r"\[(\w*)\]", source)
163 if match:
164 self.options.append(match.group(1))
165
166 parts = map(str.strip, source.split("="))
167 self.default_value = parts[1] if len(parts) != 1 else None
168
169 param_decl = parts[0]
170 min_type_tokens = 2 if re.match("(const|unsigned long) ", param_decl) el se 1
171
172 if len(param_decl.split(" ")) > min_type_tokens:
173 parts = param_decl.split(" ")
174 self.type = " ".join(parts[:-1])
175 self.name = parts[-1]
176 else:
177 self.type = param_decl
178 self.name = build_param_name(self.type)
179
180 self.value = self.name
181 self.is_prp = re.match(r"PassRefPtr<", param_decl) is not None
182 if self.is_prp:
183 self.name = "prp" + self.name[0].upper() + self.name[1:]
184 self.inner_type = re.match(r"PassRefPtr<(.+)>", param_decl).group(1)
185
186 if self.type[-1] == "*" and "char" not in self.type:
187 self.member_type = "Member<%s>" % self.type[:-1]
188 else:
189 self.member_type = self.type
190
191
192 def build_param_name(param_type):
193 base_name = re.match(r"(const |PassRefPtr<)?(\w*)", param_type).group(2)
194 return "param" + base_name
195
196
197 cmdline_parser = optparse.OptionParser()
198 cmdline_parser.add_option("--output_dir")
199 cmdline_parser.add_option("--template_dir")
200
201 try:
202 arg_options, arg_values = cmdline_parser.parse_args()
203 if len(arg_values) != 1:
204 raise Exception("Exactly one plain argument expected (found %s)" % len(a rg_values))
205 input_path = arg_values[0]
206 output_dirpath = arg_options.output_dir
207 if not output_dirpath:
208 raise Exception("Output directory must be specified")
209 except Exception:
210 # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
211 exc = sys.exc_info()[1]
212 sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc)
213 sys.stderr.write("Usage: <script> --output_dir <output_dir> InstrumentingPro bes.idl\n")
214 exit(1)
215
216 jinja_env = initialize_jinja_env(output_dirpath)
217 all_agents = set()
218 all_defines = []
219 base_name = os.path.splitext(os.path.basename(input_path))[0]
220
221 fin = open(input_path, "r")
222 files = load_model_from_idl(fin.read())
223 fin.close()
224
225 for f in files:
226 for declaration in f.declarations:
227 for agent in declaration.agents:
228 all_agents.add(agent)
229 all_defines += f.defines
230
231 template_context = {
232 "files": files,
233 "agents": all_agents,
234 "defines": all_defines,
235 "name": base_name,
236 "input_file": os.path.basename(input_path)
237 }
238 cpp_template = jinja_env.get_template("/InstrumentingProbesImpl.cpp.tmpl")
239 cpp_file = open(output_dirpath + "/" + base_name + "Impl.cpp", "w")
240 cpp_file.write(cpp_template.render(template_context))
241 cpp_file.close()
242
243 sink_h_template = jinja_env.get_template("/ProbeSink.h.tmpl")
244 sink_h_file = open(output_dirpath + "/" + to_singular(base_name) + "Sink.h", "w" )
245 sink_h_file.write(sink_h_template.render(template_context))
246 sink_h_file.close()
247
248 for f in files:
249 template_context["file"] = f
250 h_template = jinja_env.get_template("/InstrumentingProbesInl.h.tmpl")
251 h_file = open(output_dirpath + "/" + f.header_name + ".h", "w")
252 h_file.write(h_template.render(template_context))
253 h_file.close()
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/platform/BUILD.gn ('k') | third_party/WebKit/Source/platform/probe/PlatformProbes.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698