| OLD | NEW |
| 1 # Copyright 2017 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import ast |
| 5 import optparse | 6 import optparse |
| 6 import os.path | 7 import os.path |
| 7 import re | 8 import re |
| 8 import sys | 9 import sys |
| 9 | 10 |
| 10 # Path handling for libraries and templates | 11 # Path handling for libraries and templates |
| 11 # Paths have to be normalized because Jinja uses the exact template path to | 12 # 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 # 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 # to be concurrency-safe. Use absolute path because __file__ is absolute if |
| 14 # module is imported, and relative if executed directly. | 15 # module is imported, and relative if executed directly. |
| 15 # If paths differ between pre-caching and individual file compilation, the cache | 16 # 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 # 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 # 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 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(module_path, os.pardir, os.pardi
r, os.pardir, os.pardir)) |
| 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. | 21 # jinja2 is in chromium's third_party directory. |
| 23 # Insert at 1 so at front to override system libraries, and | 22 # Insert at 1 so at front to override system libraries, and |
| 24 # after path[0] == invoking script dir | 23 # after path[0] == invoking script dir |
| 25 sys.path.insert(1, third_party_dir) | 24 sys.path.insert(1, third_party_dir) |
| 26 import jinja2 | 25 import jinja2 |
| 27 | 26 |
| 28 | 27 |
| 28 def _json5_loads(lines): |
| 29 # Use json5.loads when json5 is available. Currently we use simple |
| 30 # regexs to convert well-formed JSON5 to PYL format. |
| 31 # Strip away comments and quote unquoted keys. |
| 32 re_comment = re.compile(r"^\s*//.*$|//+ .*$", re.MULTILINE) |
| 33 re_map_keys = re.compile(r"^\s*([$A-Za-z_][\w]*)\s*:", re.MULTILINE) |
| 34 pyl = re.sub(re_map_keys, r"'\1':", re.sub(re_comment, "", lines)) |
| 35 # Convert map values of true/false to Python version True/False. |
| 36 re_true = re.compile(r":\s*true\b") |
| 37 re_false = re.compile(r":\s*false\b") |
| 38 pyl = re.sub(re_true, ":True", re.sub(re_false, ":False", pyl)) |
| 39 return ast.literal_eval(pyl) |
| 40 |
| 41 |
| 29 def to_singular(text): | 42 def to_singular(text): |
| 30 return text[:-1] if text[-1] == "s" else text | 43 return text[:-1] if text[-1] == "s" else text |
| 31 | 44 |
| 32 | 45 |
| 33 def to_lower_case(name): | 46 def to_lower_case(name): |
| 34 return name[:1].lower() + name[1:] | 47 return name[:1].lower() + name[1:] |
| 35 | 48 |
| 36 | 49 |
| 50 def agent_config(agent_name, field): |
| 51 observers = config["observers"] |
| 52 if agent_name not in observers: |
| 53 return None |
| 54 return observers[agent_name][field] if field in observers[agent_name] else N
one |
| 55 |
| 56 |
| 37 def agent_name_to_class(agent_name): | 57 def agent_name_to_class(agent_name): |
| 38 if agent_name == "Performance": | 58 return agent_config(agent_name, "class") or "Inspector%sAgent" % agent_name |
| 39 return "PerformanceMonitor" | 59 |
| 40 elif agent_name == "TraceEvents": | 60 |
| 41 return "InspectorTraceEvents" | 61 def agent_name_to_include(agent_name): |
| 42 elif agent_name == "PlatformTraceEvents": | 62 include_path = agent_config(agent_name, "include") or config["settings"]["de
fault_include"] |
| 43 return "PlatformTraceEventsAgent" | 63 return os.path.join(include_path, agent_name_to_class(agent_name) + ".h") |
| 44 else: | |
| 45 return "Inspector%sAgent" % agent_name | |
| 46 | 64 |
| 47 | 65 |
| 48 def initialize_jinja_env(cache_dir): | 66 def initialize_jinja_env(cache_dir): |
| 49 jinja_env = jinja2.Environment( | 67 jinja_env = jinja2.Environment( |
| 50 loader=jinja2.FileSystemLoader(templates_dir), | 68 loader=jinja2.FileSystemLoader(os.path.join(module_path, "templates")), |
| 51 # Bytecode cache is not concurrency-safe unless pre-cached: | 69 # Bytecode cache is not concurrency-safe unless pre-cached: |
| 52 # if pre-cached this is read-only, but writing creates a race condition. | 70 # if pre-cached this is read-only, but writing creates a race condition. |
| 53 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), | 71 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), |
| 54 keep_trailing_newline=True, # newline-terminate generated files | 72 keep_trailing_newline=True, # newline-terminate generated files |
| 55 lstrip_blocks=True, # so can indent control flow tags | 73 lstrip_blocks=True, # so can indent control flow tags |
| 56 trim_blocks=True) | 74 trim_blocks=True) |
| 57 jinja_env.filters.update({ | 75 jinja_env.filters.update({ |
| 58 "to_lower_case": to_lower_case, | 76 "to_lower_case": to_lower_case, |
| 59 "to_singular": to_singular, | 77 "to_singular": to_singular, |
| 60 "agent_name_to_class": agent_name_to_class}) | 78 "agent_name_to_class": agent_name_to_class, |
| 79 "agent_name_to_include": agent_name_to_include}) |
| 61 jinja_env.add_extension('jinja2.ext.loopcontrols') | 80 jinja_env.add_extension('jinja2.ext.loopcontrols') |
| 62 return jinja_env | 81 return jinja_env |
| 63 | 82 |
| 64 | 83 |
| 65 def match_and_consume(pattern, source): | 84 def match_and_consume(pattern, source): |
| 66 match = re.match(pattern, source) | 85 match = re.match(pattern, source) |
| 67 if match: | 86 if match: |
| 68 return match, source[len(match.group(0)):].strip() | 87 return match, source[len(match.group(0)):].strip() |
| 69 return None, source | 88 return None, source |
| 70 | 89 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 84 return model | 103 return model |
| 85 | 104 |
| 86 | 105 |
| 87 class File(object): | 106 class File(object): |
| 88 def __init__(self, name, source): | 107 def __init__(self, name, source): |
| 89 self.name = name | 108 self.name = name |
| 90 self.header_name = self.name + "Inl" | 109 self.header_name = self.name + "Inl" |
| 91 self.includes = [include_inspector_header(base_name)] | 110 self.includes = [include_inspector_header(base_name)] |
| 92 self.forward_declarations = [] | 111 self.forward_declarations = [] |
| 93 self.declarations = [] | 112 self.declarations = [] |
| 94 self.defines = [] | |
| 95 for line in map(str.strip, source.split("\n")): | 113 for line in map(str.strip, source.split("\n")): |
| 96 line = re.sub(r"\s{2,}", " ", line).strip() # Collapse whitespace | 114 line = re.sub(r"\s{2,}", " ", line).strip() # Collapse whitespace |
| 97 if len(line) == 0: | 115 if len(line) == 0: |
| 98 continue | 116 continue |
| 99 if line.startswith("#define"): | |
| 100 self.defines.append(line) | |
| 101 elif line.startswith("#include"): | 117 elif line.startswith("#include"): |
| 102 self.includes.append(line) | 118 self.includes.append(line) |
| 103 elif line.startswith("class ") or line.startswith("struct "): | 119 elif line.startswith("class ") or line.startswith("struct "): |
| 104 self.forward_declarations.append(line) | 120 self.forward_declarations.append(line) |
| 105 else: | 121 else: |
| 106 self.declarations.append(Method(line)) | 122 self.declarations.append(Method(line)) |
| 107 self.includes.sort() | 123 self.includes.sort() |
| 108 self.forward_declarations.sort() | 124 self.forward_declarations.sort() |
| 109 | 125 |
| 110 | 126 |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 187 self.member_type = "Member<%s>" % self.type[:-1] | 203 self.member_type = "Member<%s>" % self.type[:-1] |
| 188 else: | 204 else: |
| 189 self.member_type = self.type | 205 self.member_type = self.type |
| 190 | 206 |
| 191 | 207 |
| 192 def build_param_name(param_type): | 208 def build_param_name(param_type): |
| 193 base_name = re.match(r"(const |PassRefPtr<)?(\w*)", param_type).group(2) | 209 base_name = re.match(r"(const |PassRefPtr<)?(\w*)", param_type).group(2) |
| 194 return "param" + base_name | 210 return "param" + base_name |
| 195 | 211 |
| 196 | 212 |
| 213 def load_config(file_name): |
| 214 default_config = { |
| 215 "settings": {}, |
| 216 "observers": {} |
| 217 } |
| 218 if not file_name: |
| 219 return default_config |
| 220 with open(file_name) as config_file: |
| 221 return _json5_loads(config_file.read()) or default_config |
| 222 |
| 223 |
| 197 cmdline_parser = optparse.OptionParser() | 224 cmdline_parser = optparse.OptionParser() |
| 198 cmdline_parser.add_option("--output_dir") | 225 cmdline_parser.add_option("--output_dir") |
| 199 cmdline_parser.add_option("--template_dir") | 226 cmdline_parser.add_option("--config") |
| 200 | 227 |
| 201 try: | 228 try: |
| 202 arg_options, arg_values = cmdline_parser.parse_args() | 229 arg_options, arg_values = cmdline_parser.parse_args() |
| 203 if len(arg_values) != 1: | 230 if len(arg_values) != 1: |
| 204 raise Exception("Exactly one plain argument expected (found %s)" % len(a
rg_values)) | 231 raise Exception("Exactly one plain argument expected (found %s)" % len(a
rg_values)) |
| 205 input_path = arg_values[0] | 232 input_path = arg_values[0] |
| 206 output_dirpath = arg_options.output_dir | 233 output_dirpath = arg_options.output_dir |
| 207 if not output_dirpath: | 234 if not output_dirpath: |
| 208 raise Exception("Output directory must be specified") | 235 raise Exception("Output directory must be specified") |
| 236 config_file_name = arg_options.config |
| 209 except Exception: | 237 except Exception: |
| 210 # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html | 238 # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html |
| 211 exc = sys.exc_info()[1] | 239 exc = sys.exc_info()[1] |
| 212 sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc) | 240 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") | 241 sys.stderr.write("Usage: <script> [options] <probes.pidl>\n") |
| 242 sys.stderr.write("Options:\n") |
| 243 sys.stderr.write("\t--config <config_file.json5>\n") |
| 244 sys.stderr.write("\t--output_dir <output_dir>\n") |
| 214 exit(1) | 245 exit(1) |
| 215 | 246 |
| 247 config = load_config(config_file_name) |
| 216 jinja_env = initialize_jinja_env(output_dirpath) | 248 jinja_env = initialize_jinja_env(output_dirpath) |
| 217 all_agents = set() | 249 all_agents = set() |
| 218 all_defines = [] | |
| 219 base_name = os.path.splitext(os.path.basename(input_path))[0] | 250 base_name = os.path.splitext(os.path.basename(input_path))[0] |
| 220 | 251 |
| 221 fin = open(input_path, "r") | 252 fin = open(input_path, "r") |
| 222 files = load_model_from_idl(fin.read()) | 253 files = load_model_from_idl(fin.read()) |
| 223 fin.close() | 254 fin.close() |
| 224 | 255 |
| 225 for f in files: | 256 for f in files: |
| 226 for declaration in f.declarations: | 257 for declaration in f.declarations: |
| 227 for agent in declaration.agents: | 258 for agent in declaration.agents: |
| 228 all_agents.add(agent) | 259 all_agents.add(agent) |
| 229 all_defines += f.defines | |
| 230 | 260 |
| 231 template_context = { | 261 template_context = { |
| 232 "files": files, | 262 "files": files, |
| 233 "agents": all_agents, | 263 "agents": all_agents, |
| 234 "defines": all_defines, | 264 "config": config, |
| 235 "name": base_name, | 265 "name": base_name, |
| 236 "input_file": os.path.basename(input_path) | 266 "input_file": os.path.basename(input_path) |
| 237 } | 267 } |
| 238 cpp_template = jinja_env.get_template("/InstrumentingProbesImpl.cpp.tmpl") | 268 cpp_template = jinja_env.get_template("/InstrumentingProbesImpl.cpp.tmpl") |
| 239 cpp_file = open(output_dirpath + "/" + base_name + "Impl.cpp", "w") | 269 cpp_file = open(output_dirpath + "/" + base_name + "Impl.cpp", "w") |
| 240 cpp_file.write(cpp_template.render(template_context)) | 270 cpp_file.write(cpp_template.render(template_context)) |
| 241 cpp_file.close() | 271 cpp_file.close() |
| 242 | 272 |
| 243 sink_h_template = jinja_env.get_template("/ProbeSink.h.tmpl") | 273 sink_h_template = jinja_env.get_template("/ProbeSink.h.tmpl") |
| 244 sink_h_file = open(output_dirpath + "/" + to_singular(base_name) + "Sink.h", "w"
) | 274 sink_h_file = open(output_dirpath + "/" + to_singular(base_name) + "Sink.h", "w"
) |
| 245 sink_h_file.write(sink_h_template.render(template_context)) | 275 sink_h_file.write(sink_h_template.render(template_context)) |
| 246 sink_h_file.close() | 276 sink_h_file.close() |
| 247 | 277 |
| 248 for f in files: | 278 for f in files: |
| 249 template_context["file"] = f | 279 template_context["file"] = f |
| 250 h_template = jinja_env.get_template("/InstrumentingProbesInl.h.tmpl") | 280 h_template = jinja_env.get_template("/InstrumentingProbesInl.h.tmpl") |
| 251 h_file = open(output_dirpath + "/" + f.header_name + ".h", "w") | 281 h_file = open(output_dirpath + "/" + f.header_name + ".h", "w") |
| 252 h_file.write(h_template.render(template_context)) | 282 h_file.write(h_template.render(template_context)) |
| 253 h_file.close() | 283 h_file.close() |
| OLD | NEW |