| 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 = 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() | |
| OLD | NEW |