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