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