| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 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 os.path | 5 import os.path |
| 6 import sys | 6 import sys |
| 7 import optparse | 7 import optparse |
| 8 import collections | 8 import collections |
| 9 import functools | 9 import functools |
| 10 import re |
| 10 try: | 11 try: |
| 11 import json | 12 import json |
| 12 except ImportError: | 13 except ImportError: |
| 13 import simplejson as json | 14 import simplejson as json |
| 14 | 15 |
| 15 # Path handling for libraries and templates | 16 # Path handling for libraries and templates |
| 16 # Paths have to be normalized because Jinja uses the exact template path to | 17 # Paths have to be normalized because Jinja uses the exact template path to |
| 17 # determine the hash used in the cache filename, and we need a pre-caching step | 18 # determine the hash used in the cache filename, and we need a pre-caching step |
| 18 # to be concurrency-safe. Use absolute path because __file__ is absolute if | 19 # to be concurrency-safe. Use absolute path because __file__ is absolute if |
| 19 # module is imported, and relative if executed directly. | 20 # module is imported, and relative if executed directly. |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 72 exc = sys.exc_info()[1] | 73 exc = sys.exc_info()[1] |
| 73 sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc) | 74 sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc) |
| 74 exit(1) | 75 exit(1) |
| 75 | 76 |
| 76 try: | 77 try: |
| 77 config_json_file = open(config_file, "r") | 78 config_json_file = open(config_file, "r") |
| 78 config_json_string = config_json_file.read() | 79 config_json_string = config_json_file.read() |
| 79 config_partial = json_to_object(config_json_string, output_base, config_
base) | 80 config_partial = json_to_object(config_json_string, output_base, config_
base) |
| 80 config_json_file.close() | 81 config_json_file.close() |
| 81 defaults = { | 82 defaults = { |
| 83 ".use_snake_file_names": False, |
| 84 ".use_title_case_methods": False, |
| 82 ".imported": False, | 85 ".imported": False, |
| 83 ".imported.export_macro": "", | 86 ".imported.export_macro": "", |
| 84 ".imported.export_header": False, | 87 ".imported.export_header": False, |
| 85 ".imported.header": False, | 88 ".imported.header": False, |
| 86 ".imported.package": False, | 89 ".imported.package": False, |
| 87 ".protocol.export_macro": "", | 90 ".protocol.export_macro": "", |
| 88 ".protocol.export_header": False, | 91 ".protocol.export_header": False, |
| 89 ".protocol.options": False, | 92 ".protocol.options": False, |
| 90 ".exported": False, | 93 ".exported": False, |
| 91 ".exported.export_macro": "", | 94 ".exported.export_macro": "", |
| (...skipping 19 matching lines...) Expand all Loading... |
| 111 | 114 |
| 112 | 115 |
| 113 def dash_to_camelcase(word): | 116 def dash_to_camelcase(word): |
| 114 prefix = "" | 117 prefix = "" |
| 115 if word[0] == "-": | 118 if word[0] == "-": |
| 116 prefix = "Negative" | 119 prefix = "Negative" |
| 117 word = word[1:] | 120 word = word[1:] |
| 118 return prefix + "".join(to_title_case(x) or "-" for x in word.split("-")) | 121 return prefix + "".join(to_title_case(x) or "-" for x in word.split("-")) |
| 119 | 122 |
| 120 | 123 |
| 121 def initialize_jinja_env(jinja_dir, cache_dir): | 124 def to_snake_case(name): |
| 125 return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name, sys.maxint).lower() |
| 126 |
| 127 |
| 128 def to_method_case(config, name): |
| 129 if config.use_title_case_methods: |
| 130 return to_title_case(name) |
| 131 return name |
| 132 |
| 133 |
| 134 def initialize_jinja_env(jinja_dir, cache_dir, config): |
| 122 # pylint: disable=F0401 | 135 # pylint: disable=F0401 |
| 123 sys.path.insert(1, os.path.abspath(jinja_dir)) | 136 sys.path.insert(1, os.path.abspath(jinja_dir)) |
| 124 import jinja2 | 137 import jinja2 |
| 125 | 138 |
| 126 jinja_env = jinja2.Environment( | 139 jinja_env = jinja2.Environment( |
| 127 loader=jinja2.FileSystemLoader(module_path), | 140 loader=jinja2.FileSystemLoader(module_path), |
| 128 # Bytecode cache is not concurrency-safe unless pre-cached: | 141 # Bytecode cache is not concurrency-safe unless pre-cached: |
| 129 # if pre-cached this is read-only, but writing creates a race condition. | 142 # if pre-cached this is read-only, but writing creates a race condition. |
| 130 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), | 143 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), |
| 131 keep_trailing_newline=True, # newline-terminate generated files | 144 keep_trailing_newline=True, # newline-terminate generated files |
| 132 lstrip_blocks=True, # so can indent control flow tags | 145 lstrip_blocks=True, # so can indent control flow tags |
| 133 trim_blocks=True) | 146 trim_blocks=True) |
| 134 jinja_env.filters.update({"to_title_case": to_title_case, "dash_to_camelcase
": dash_to_camelcase}) | 147 jinja_env.filters.update({"to_title_case": to_title_case, "dash_to_camelcase
": dash_to_camelcase, "to_method_case": functools.partial(to_method_case, config
)}) |
| 135 jinja_env.add_extension("jinja2.ext.loopcontrols") | 148 jinja_env.add_extension("jinja2.ext.loopcontrols") |
| 136 return jinja_env | 149 return jinja_env |
| 137 | 150 |
| 138 | 151 |
| 139 def patch_full_qualified_refs(protocol): | 152 def patch_full_qualified_refs(protocol): |
| 140 def patch_full_qualified_refs_in_domain(json, domain_name): | 153 def patch_full_qualified_refs_in_domain(json, domain_name): |
| 141 if isinstance(json, list): | 154 if isinstance(json, list): |
| 142 for item in json: | 155 for item in json: |
| 143 patch_full_qualified_refs_in_domain(item, domain_name) | 156 patch_full_qualified_refs_in_domain(item, domain_name) |
| 144 | 157 |
| 145 if not isinstance(json, dict): | 158 if not isinstance(json, dict): |
| 146 return | 159 return |
| 147 for key in json: | 160 for key in json: |
| 148 if key == "type" and json[key] == "string": | 161 if key == "type" and json[key] == "string": |
| 149 json[key] = domain_name + ".string" | 162 json[key] = domain_name + ".string" |
| 150 if key != "$ref": | 163 if key != "$ref": |
| 151 patch_full_qualified_refs_in_domain(json[key], domain_name) | 164 patch_full_qualified_refs_in_domain(json[key], domain_name) |
| 152 continue | 165 continue |
| 153 if json["$ref"].find(".") == -1: | 166 if json["$ref"].find(".") == -1: |
| 154 json["$ref"] = domain_name + "." + json["$ref"] | 167 json["$ref"] = domain_name + "." + json["$ref"] |
| 155 return | 168 return |
| 156 | 169 |
| 157 for domain in protocol.json_api["domains"]: | 170 for domain in protocol.json_api["domains"]: |
| 158 patch_full_qualified_refs_in_domain(domain, domain["domain"]) | 171 patch_full_qualified_refs_in_domain(domain, domain["domain"]) |
| 159 | 172 |
| 160 | 173 |
| 161 def calculate_exports(protocol): | 174 def calculate_imports_and_exports(config, protocol): |
| 162 def calculate_exports_in_json(json_value): | 175 def has_exports(json_value, clear): |
| 163 has_exports = False | 176 result = False |
| 164 if isinstance(json_value, list): | 177 if isinstance(json_value, list): |
| 165 for item in json_value: | 178 for item in json_value: |
| 166 has_exports = calculate_exports_in_json(item) or has_exports | 179 result = has_exports(item, clear) or result |
| 167 if isinstance(json_value, dict): | 180 if isinstance(json_value, dict): |
| 168 has_exports = ("exported" in json_value and json_value["exported"])
or has_exports | 181 if "exported" in json_value and json_value["exported"]: |
| 182 result = True |
| 183 if "exported" in json_value and clear: |
| 184 del json_value["exported"] |
| 169 for key in json_value: | 185 for key in json_value: |
| 170 has_exports = calculate_exports_in_json(json_value[key]) or has_
exports | 186 result = has_exports(json_value[key], clear) or result |
| 171 return has_exports | 187 return result |
| 172 | 188 |
| 173 protocol.json_api["has_exports"] = False | 189 imported_domains = protocol.imported_domains |
| 190 if config.protocol.options: |
| 191 protocol.generate_domains = [rule.domain for rule in config.protocol.opt
ions] |
| 192 imported_domains = list(set(protocol.imported_domains) - set(protocol.ge
nerate_domains)) |
| 193 exported_domains = protocol.generate_domains |
| 194 |
| 195 protocol.imported_domains = [] |
| 196 protocol.exported_domains = [] |
| 174 for domain_json in protocol.json_api["domains"]: | 197 for domain_json in protocol.json_api["domains"]: |
| 175 domain_json["has_exports"] = calculate_exports_in_json(domain_json) | 198 domain = domain_json["domain"] |
| 176 if domain_json["has_exports"] and domain_json["domain"] in protocol.gene
rate_domains: | 199 clear = domain not in exported_domains and domain not in imported_domain
s |
| 177 protocol.json_api["has_exports"] = True | 200 if not has_exports(domain_json, clear): |
| 201 continue |
| 202 if domain in exported_domains: |
| 203 protocol.exported_domains.append(domain) |
| 204 if domain in imported_domains: |
| 205 protocol.imported_domains.append(domain) |
| 178 | 206 |
| 179 | 207 |
| 180 def create_imported_type_definition(domain_name, type, imported_namespace): | 208 def create_imported_type_definition(domain_name, type, imported_namespace): |
| 181 # pylint: disable=W0622 | 209 # pylint: disable=W0622 |
| 182 return { | 210 return { |
| 183 "return_type": "std::unique_ptr<%s::%s::API::%s>" % (imported_namespace,
domain_name, type["id"]), | 211 "return_type": "std::unique_ptr<%s::%s::API::%s>" % (imported_namespace,
domain_name, type["id"]), |
| 184 "pass_type": "std::unique_ptr<%s::%s::API::%s>" % (imported_namespace, d
omain_name, type["id"]), | 212 "pass_type": "std::unique_ptr<%s::%s::API::%s>" % (imported_namespace, d
omain_name, type["id"]), |
| 185 "to_raw_type": "%s.get()", | 213 "to_raw_type": "%s.get()", |
| 186 "to_pass_type": "std::move(%s)", | 214 "to_pass_type": "std::move(%s)", |
| 187 "to_rvalue": "std::move(%s)", | 215 "to_rvalue": "std::move(%s)", |
| (...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 387 | 415 |
| 388 def generate_disable(protocol, config, domain): | 416 def generate_disable(protocol, config, domain): |
| 389 if "commands" not in domain: | 417 if "commands" not in domain: |
| 390 return True | 418 return True |
| 391 for command in domain["commands"]: | 419 for command in domain["commands"]: |
| 392 if command["name"] == "disable" and generate_command(protocol, config, d
omain["domain"], "disable"): | 420 if command["name"] == "disable" and generate_command(protocol, config, d
omain["domain"], "disable"): |
| 393 return False | 421 return False |
| 394 return True | 422 return True |
| 395 | 423 |
| 396 | 424 |
| 397 def format_include(header): | 425 def format_include(config, header, file_name=None): |
| 398 return "\"" + header + "\"" if header[0] not in "<\"" else header | 426 if file_name is not None: |
| 427 header = header + "/" + file_name + ".h" |
| 428 header = "\"" + header + "\"" if header[0] not in "<\"" else header |
| 429 if config.use_snake_file_names: |
| 430 header = to_snake_case(header) |
| 431 return header |
| 432 |
| 433 |
| 434 def to_file_name(config, file_name): |
| 435 if config.use_snake_file_names: |
| 436 return to_snake_case(file_name).replace(".cpp", ".cc") |
| 437 return file_name |
| 399 | 438 |
| 400 | 439 |
| 401 def read_protocol_file(file_name, json_api): | 440 def read_protocol_file(file_name, json_api): |
| 402 input_file = open(file_name, "r") | 441 input_file = open(file_name, "r") |
| 403 json_string = input_file.read() | 442 json_string = input_file.read() |
| 404 input_file.close() | 443 input_file.close() |
| 405 parsed_json = json.loads(json_string) | 444 parsed_json = json.loads(json_string) |
| 406 version = parsed_json["version"]["major"] + "." + parsed_json["version"]["mi
nor"] | 445 version = parsed_json["version"]["major"] + "." + parsed_json["version"]["mi
nor"] |
| 407 domains = [] | 446 domains = [] |
| 408 for domain in parsed_json["domains"]: | 447 for domain in parsed_json["domains"]: |
| 409 domains.append(domain["domain"]) | 448 domains.append(domain["domain"]) |
| 410 domain["version"] = version | 449 domain["version"] = version |
| 411 json_api["domains"] += parsed_json["domains"] | 450 json_api["domains"] += parsed_json["domains"] |
| 412 return domains | 451 return domains |
| 413 | 452 |
| 414 | 453 |
| 415 class Protocol(object): | 454 class Protocol(object): |
| 416 def __init__(self): | 455 def __init__(self): |
| 417 self.json_api = {} | 456 self.json_api = {} |
| 418 self.generate_domains = [] | 457 self.generate_domains = [] |
| 419 self.imported_domains = [] | 458 self.imported_domains = [] |
| 459 self.exported_domains = [] |
| 420 | 460 |
| 421 | 461 |
| 422 def main(): | 462 def main(): |
| 423 jinja_dir, config_file, config = read_config() | 463 jinja_dir, config_file, config = read_config() |
| 424 | 464 |
| 425 protocol = Protocol() | 465 protocol = Protocol() |
| 426 protocol.json_api = {"domains": []} | 466 protocol.json_api = {"domains": []} |
| 427 protocol.generate_domains = read_protocol_file(config.protocol.path, protoco
l.json_api) | 467 protocol.generate_domains = read_protocol_file(config.protocol.path, protoco
l.json_api) |
| 428 protocol.imported_domains = read_protocol_file(config.imported.path, protoco
l.json_api) if config.imported else [] | 468 protocol.imported_domains = read_protocol_file(config.imported.path, protoco
l.json_api) if config.imported else [] |
| 429 patch_full_qualified_refs(protocol) | 469 patch_full_qualified_refs(protocol) |
| 430 calculate_exports(protocol) | 470 calculate_imports_and_exports(config, protocol) |
| 431 create_type_definitions(protocol, "::".join(config.imported.namespace) if co
nfig.imported else "") | 471 create_type_definitions(protocol, "::".join(config.imported.namespace) if co
nfig.imported else "") |
| 432 | 472 |
| 433 if not config.exported: | 473 if not config.exported and len(protocol.exported_domains): |
| 434 for domain_json in protocol.json_api["domains"]: | 474 sys.stderr.write("Domains [%s] are exported, but config is missing expor
t entry\n\n" % ", ".join(protocol.exported_domains)) |
| 435 if domain_json["has_exports"] and domain_json["domain"] in protocol.
generate_domains: | 475 exit(1) |
| 436 sys.stderr.write("Domain %s is exported, but config is missing e
xport entry\n\n" % domain_json["domain"]) | |
| 437 exit(1) | |
| 438 | 476 |
| 439 if not os.path.exists(config.protocol.output): | 477 if not os.path.exists(config.protocol.output): |
| 440 os.mkdir(config.protocol.output) | 478 os.mkdir(config.protocol.output) |
| 441 if protocol.json_api["has_exports"] and not os.path.exists(config.exported.o
utput): | 479 if len(protocol.exported_domains) and not os.path.exists(config.exported.out
put): |
| 442 os.mkdir(config.exported.output) | 480 os.mkdir(config.exported.output) |
| 443 jinja_env = initialize_jinja_env(jinja_dir, config.protocol.output) | 481 jinja_env = initialize_jinja_env(jinja_dir, config.protocol.output, config) |
| 444 | 482 |
| 445 inputs = [] | 483 inputs = [] |
| 446 inputs.append(__file__) | 484 inputs.append(__file__) |
| 447 inputs.append(config_file) | 485 inputs.append(config_file) |
| 448 inputs.append(config.protocol.path) | 486 inputs.append(config.protocol.path) |
| 449 if config.imported: | 487 if config.imported: |
| 450 inputs.append(config.imported.path) | 488 inputs.append(config.imported.path) |
| 451 templates_dir = os.path.join(module_path, "templates") | 489 templates_dir = os.path.join(module_path, "templates") |
| 452 inputs.append(os.path.join(templates_dir, "TypeBuilder_h.template")) | 490 inputs.append(os.path.join(templates_dir, "TypeBuilder_h.template")) |
| 453 inputs.append(os.path.join(templates_dir, "TypeBuilder_cpp.template")) | 491 inputs.append(os.path.join(templates_dir, "TypeBuilder_cpp.template")) |
| (...skipping 12 matching lines...) Expand all Loading... |
| 466 template_context = { | 504 template_context = { |
| 467 "config": config, | 505 "config": config, |
| 468 "domain": domain, | 506 "domain": domain, |
| 469 "join_arrays": join_arrays, | 507 "join_arrays": join_arrays, |
| 470 "resolve_type": functools.partial(resolve_type, protocol), | 508 "resolve_type": functools.partial(resolve_type, protocol), |
| 471 "type_definition": functools.partial(type_definition, protocol), | 509 "type_definition": functools.partial(type_definition, protocol), |
| 472 "generate_command": functools.partial(generate_command, protocol, co
nfig), | 510 "generate_command": functools.partial(generate_command, protocol, co
nfig), |
| 473 "generate_event": functools.partial(generate_event, protocol, config
), | 511 "generate_event": functools.partial(generate_event, protocol, config
), |
| 474 "is_async_command": functools.partial(is_async_command, protocol, co
nfig), | 512 "is_async_command": functools.partial(is_async_command, protocol, co
nfig), |
| 475 "generate_disable": functools.partial(generate_disable, protocol, co
nfig), | 513 "generate_disable": functools.partial(generate_disable, protocol, co
nfig), |
| 476 "format_include": format_include | 514 "format_include": functools.partial(format_include, config), |
| 477 } | 515 } |
| 478 | 516 |
| 479 if domain["domain"] in protocol.generate_domains: | 517 if domain["domain"] in protocol.generate_domains: |
| 480 outputs[os.path.join(config.protocol.output, class_name + ".h")] = h
_template.render(template_context) | 518 outputs[os.path.join(config.protocol.output, to_file_name(config, cl
ass_name + ".h"))] = h_template.render(template_context) |
| 481 outputs[os.path.join(config.protocol.output, class_name + ".cpp")] =
cpp_template.render(template_context) | 519 outputs[os.path.join(config.protocol.output, to_file_name(config, cl
ass_name + ".cpp"))] = cpp_template.render(template_context) |
| 482 if domain["has_exports"]: | 520 if domain["domain"] in protocol.exported_domains: |
| 483 outputs[os.path.join(config.exported.output, class_name + ".h")]
= exported_template.render(template_context) | 521 outputs[os.path.join(config.exported.output, to_file_name(config
, class_name + ".h"))] = exported_template.render(template_context) |
| 484 if domain["domain"] in protocol.imported_domains and domain["has_exports
"]: | 522 if domain["domain"] in protocol.imported_domains: |
| 485 outputs[os.path.join(config.protocol.output, class_name + ".h")] = i
mported_template.render(template_context) | 523 outputs[os.path.join(config.protocol.output, to_file_name(config, cl
ass_name + ".h"))] = imported_template.render(template_context) |
| 486 | 524 |
| 487 if config.lib: | 525 if config.lib: |
| 488 template_context = { | 526 template_context = { |
| 489 "config": config, | 527 "config": config, |
| 490 "format_include": format_include, | 528 "format_include": functools.partial(format_include, config), |
| 491 } | 529 } |
| 492 | 530 |
| 493 lib_templates_dir = os.path.join(module_path, "lib") | 531 lib_templates_dir = os.path.join(module_path, "lib") |
| 494 # Note these should be sorted in the right order. | 532 # Note these should be sorted in the right order. |
| 495 # TODO(dgozman): sort them programmatically based on commented includes. | 533 # TODO(dgozman): sort them programmatically based on commented includes. |
| 496 lib_h_templates = [ | 534 lib_h_templates = [ |
| 497 "Collections_h.template", | 535 "Collections_h.template", |
| 498 "ErrorSupport_h.template", | 536 "ErrorSupport_h.template", |
| 499 "Values_h.template", | 537 "Values_h.template", |
| 500 "Object_h.template", | 538 "Object_h.template", |
| (...skipping 20 matching lines...) Expand all Loading... |
| 521 ] | 559 ] |
| 522 | 560 |
| 523 def generate_lib_file(file_name, template_files): | 561 def generate_lib_file(file_name, template_files): |
| 524 parts = [] | 562 parts = [] |
| 525 for template_file in template_files: | 563 for template_file in template_files: |
| 526 inputs.append(os.path.join(lib_templates_dir, template_file)) | 564 inputs.append(os.path.join(lib_templates_dir, template_file)) |
| 527 template = jinja_env.get_template("lib/" + template_file) | 565 template = jinja_env.get_template("lib/" + template_file) |
| 528 parts.append(template.render(template_context)) | 566 parts.append(template.render(template_context)) |
| 529 outputs[file_name] = "\n\n".join(parts) | 567 outputs[file_name] = "\n\n".join(parts) |
| 530 | 568 |
| 531 generate_lib_file(os.path.join(config.lib.output, "Forward.h"), forward_
h_templates) | 569 generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "
Forward.h")), forward_h_templates) |
| 532 generate_lib_file(os.path.join(config.lib.output, "Protocol.h"), lib_h_t
emplates) | 570 generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "
Protocol.h")), lib_h_templates) |
| 533 generate_lib_file(os.path.join(config.lib.output, "Protocol.cpp"), lib_c
pp_templates) | 571 generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "
Protocol.cpp")), lib_cpp_templates) |
| 534 | 572 |
| 535 # Make gyp / make generatos happy, otherwise make rebuilds world. | 573 # Make gyp / make generatos happy, otherwise make rebuilds world. |
| 536 inputs_ts = max(map(os.path.getmtime, inputs)) | 574 inputs_ts = max(map(os.path.getmtime, inputs)) |
| 537 up_to_date = True | 575 up_to_date = True |
| 538 for output_file in outputs.iterkeys(): | 576 for output_file in outputs.iterkeys(): |
| 539 if not os.path.exists(output_file) or os.path.getmtime(output_file) < in
puts_ts: | 577 if not os.path.exists(output_file) or os.path.getmtime(output_file) < in
puts_ts: |
| 540 up_to_date = False | 578 up_to_date = False |
| 541 break | 579 break |
| 542 if up_to_date: | 580 if up_to_date: |
| 543 sys.exit() | 581 sys.exit() |
| 544 | 582 |
| 545 for file_name, content in outputs.iteritems(): | 583 for file_name, content in outputs.iteritems(): |
| 546 out_file = open(file_name, "w") | 584 out_file = open(file_name, "w") |
| 547 out_file.write(content) | 585 out_file.write(content) |
| 548 out_file.close() | 586 out_file.close() |
| 549 | 587 |
| 550 | 588 |
| 551 main() | 589 main() |
| OLD | NEW |