| 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 |