code_generator.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. #!/usr/bin/env python3
  2. # Copyright 2016 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import os
  6. import os.path
  7. import sys
  8. import argparse
  9. import collections
  10. import functools
  11. import re
  12. import copy
  13. try:
  14. import json
  15. except ImportError:
  16. import simplejson as json
  17. import pdl
  18. try:
  19. unicode
  20. except NameError:
  21. # Define unicode for Py3
  22. def unicode(s, *_):
  23. return s
  24. # Path handling for libraries and templates
  25. # Paths have to be normalized because Jinja uses the exact template path to
  26. # determine the hash used in the cache filename, and we need a pre-caching step
  27. # to be concurrency-safe. Use absolute path because __file__ is absolute if
  28. # module is imported, and relative if executed directly.
  29. # If paths differ between pre-caching and individual file compilation, the cache
  30. # is regenerated, which causes a race condition and breaks concurrent build,
  31. # since some compile processes will try to read the partially written cache.
  32. module_path, module_filename = os.path.split(os.path.realpath(__file__))
  33. def read_config():
  34. # pylint: disable=W0703
  35. def json_to_object(data, output_base, config_base):
  36. def json_object_hook(object_dict):
  37. items = [(k, os.path.join(config_base, v) if k == "path" else v)
  38. for (k, v) in object_dict.items()]
  39. items = [(k, os.path.join(output_base, v) if k == "output" else v)
  40. for (k, v) in items]
  41. keys, values = list(zip(*items))
  42. # 'async' is a keyword since Python 3.7.
  43. # Avoid namedtuple(rename=True) for compatibility with Python 2.X.
  44. keys = tuple('async_' if k == 'async' else k for k in keys)
  45. return collections.namedtuple('X', keys)(*values)
  46. return json.loads(data, object_hook=json_object_hook)
  47. def init_defaults(config_tuple, path, defaults):
  48. keys = list(config_tuple._fields) # pylint: disable=E1101
  49. values = [getattr(config_tuple, k) for k in keys]
  50. for i in range(len(keys)):
  51. if hasattr(values[i], "_fields"):
  52. values[i] = init_defaults(values[i], path + "." + keys[i], defaults)
  53. for optional in defaults:
  54. if optional.find(path + ".") != 0:
  55. continue
  56. optional_key = optional[len(path) + 1:]
  57. if optional_key.find(".") == -1 and optional_key not in keys:
  58. keys.append(optional_key)
  59. values.append(defaults[optional])
  60. return collections.namedtuple('X', keys)(*values)
  61. try:
  62. cmdline_parser = argparse.ArgumentParser()
  63. cmdline_parser.add_argument("--output_base", type=unicode, required=True)
  64. cmdline_parser.add_argument("--jinja_dir", type=unicode, required=True)
  65. cmdline_parser.add_argument("--config", type=unicode, required=True)
  66. cmdline_parser.add_argument("--config_value", default=[], action="append")
  67. cmdline_parser.add_argument(
  68. "--inspector_protocol_dir", type=unicode, required=True,
  69. help=("directory with code_generator.py and C++ encoding / binding "
  70. "libraries, relative to the root of the source tree."))
  71. arg_options = cmdline_parser.parse_args()
  72. jinja_dir = arg_options.jinja_dir
  73. output_base = arg_options.output_base
  74. config_file = arg_options.config
  75. config_base = os.path.dirname(config_file)
  76. config_values = arg_options.config_value
  77. inspector_protocol_dir = arg_options.inspector_protocol_dir.lstrip('/')
  78. except Exception:
  79. # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
  80. exc = sys.exc_info()[1]
  81. sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc)
  82. exit(1)
  83. try:
  84. config_json_file = open(config_file, "r")
  85. config_json_string = config_json_file.read()
  86. config_partial = json_to_object(config_json_string, output_base,
  87. config_base)
  88. config_json_file.close()
  89. defaults = {
  90. ".use_snake_file_names": False,
  91. ".use_title_case_methods": False,
  92. ".use_embedder_types": False,
  93. ".imported": False,
  94. ".imported.export_macro": "",
  95. ".imported.export_header": False,
  96. ".imported.header": False,
  97. ".imported.package": False,
  98. ".imported.options": False,
  99. ".protocol.export_macro": "",
  100. ".protocol.export_header": False,
  101. ".protocol.options": False,
  102. ".protocol.file_name_prefix": "",
  103. ".exported": False,
  104. ".exported.export_macro": "",
  105. ".exported.export_header": False,
  106. ".lib": False,
  107. ".lib.export_macro": "",
  108. ".lib.export_header": False,
  109. ".crdtp": False,
  110. ".crdtp.dir": os.path.join(inspector_protocol_dir, "crdtp"),
  111. ".crdtp.namespace": "crdtp",
  112. }
  113. for key_value in config_values:
  114. parts = key_value.split("=")
  115. if len(parts) == 2:
  116. defaults["." + parts[0]] = parts[1]
  117. return (jinja_dir, config_file, init_defaults(config_partial, "", defaults))
  118. except Exception:
  119. # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
  120. exc = sys.exc_info()[1]
  121. sys.stderr.write("Failed to parse config file: %s\n\n" % exc)
  122. exit(1)
  123. # ---- Begin of utilities exposed to generator ----
  124. def to_title_case(name):
  125. return name[:1].upper() + name[1:]
  126. def dash_to_camelcase(word):
  127. prefix = ""
  128. if word[0] == "-":
  129. prefix = "Negative"
  130. word = word[1:]
  131. return prefix + "".join(to_title_case(x) or "-" for x in word.split("-"))
  132. def to_snake_case(name):
  133. name = re.sub(r"([A-Z]{2,})([A-Z][a-z])", r"\1_\2", name)
  134. return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name, sys.maxsize).lower()
  135. def to_method_case(config, name):
  136. if config.use_title_case_methods:
  137. return to_title_case(name)
  138. return name
  139. def join_arrays(dict, keys):
  140. result = []
  141. for key in keys:
  142. if key in dict:
  143. result += dict[key]
  144. return result
  145. def format_include(config, header, file_name=None):
  146. if file_name is not None:
  147. header = header + "/" + file_name + ".h"
  148. header = "\"" + header + "\"" if header[0] not in "<\"" else header
  149. if config.use_snake_file_names:
  150. header = to_snake_case(header)
  151. return header
  152. def format_domain_include(config, header, file_name):
  153. return format_include(config, header,
  154. config.protocol.file_name_prefix + file_name)
  155. def to_file_name(config, file_name):
  156. if config.use_snake_file_names:
  157. return to_snake_case(file_name).replace(".cpp", ".cc")
  158. return file_name
  159. # ---- End of utilities exposed to generator ----
  160. def initialize_jinja_env(jinja_dir, cache_dir, config):
  161. # pylint: disable=F0401
  162. sys.path.insert(1, os.path.abspath(jinja_dir))
  163. import jinja2
  164. jinja_env = jinja2.Environment(
  165. loader=jinja2.FileSystemLoader(module_path),
  166. # Bytecode cache is not concurrency-safe unless pre-cached:
  167. # if pre-cached this is read-only, but writing creates a race condition.
  168. bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir),
  169. keep_trailing_newline=True, # newline-terminate generated files
  170. lstrip_blocks=True, # so can indent control flow tags
  171. trim_blocks=True)
  172. jinja_env.filters.update({
  173. "to_title_case": to_title_case,
  174. "dash_to_camelcase": dash_to_camelcase,
  175. "to_method_case": functools.partial(to_method_case, config)})
  176. jinja_env.add_extension("jinja2.ext.loopcontrols")
  177. return jinja_env
  178. def create_imported_type_definition(domain_name, type, imported_namespace):
  179. # pylint: disable=W0622
  180. return {
  181. "return_type": "std::unique_ptr<%s::%s::API::%s>" % (
  182. imported_namespace, domain_name, type["id"]),
  183. "pass_type": "std::unique_ptr<%s::%s::API::%s>" % (
  184. imported_namespace, domain_name, type["id"]),
  185. "to_raw_type": "%s.get()",
  186. "to_pass_type": "std::move(%s)",
  187. "to_rvalue": "std::move(%s)",
  188. "type": "std::unique_ptr<%s::%s::API::%s>" % (
  189. imported_namespace, domain_name, type["id"]),
  190. "raw_type": "%s::%s::API::%s" % (
  191. imported_namespace, domain_name, type["id"]),
  192. "raw_pass_type": "%s::%s::API::%s*" % (
  193. imported_namespace, domain_name, type["id"]),
  194. "raw_return_type": "%s::%s::API::%s*" % (
  195. imported_namespace, domain_name, type["id"]),
  196. }
  197. def create_user_type_definition(domain_name, type):
  198. # pylint: disable=W0622
  199. return {
  200. "return_type": "std::unique_ptr<protocol::%s::%s>" % (
  201. domain_name, type["id"]),
  202. "pass_type": "std::unique_ptr<protocol::%s::%s>" % (
  203. domain_name, type["id"]),
  204. "to_raw_type": "%s.get()",
  205. "to_pass_type": "std::move(%s)",
  206. "to_rvalue": "std::move(%s)",
  207. "type": "std::unique_ptr<protocol::%s::%s>" % (domain_name, type["id"]),
  208. "raw_type": "protocol::%s::%s" % (domain_name, type["id"]),
  209. "raw_pass_type": "protocol::%s::%s*" % (domain_name, type["id"]),
  210. "raw_return_type": "protocol::%s::%s*" % (domain_name, type["id"]),
  211. }
  212. def create_object_type_definition():
  213. # pylint: disable=W0622
  214. return {
  215. "return_type": "std::unique_ptr<protocol::DictionaryValue>",
  216. "pass_type": "std::unique_ptr<protocol::DictionaryValue>",
  217. "to_raw_type": "%s.get()",
  218. "to_pass_type": "std::move(%s)",
  219. "to_rvalue": "std::move(%s)",
  220. "type": "std::unique_ptr<protocol::DictionaryValue>",
  221. "raw_type": "protocol::DictionaryValue",
  222. "raw_pass_type": "protocol::DictionaryValue*",
  223. "raw_return_type": "protocol::DictionaryValue*",
  224. }
  225. def create_any_type_definition():
  226. # pylint: disable=W0622
  227. return {
  228. "return_type": "std::unique_ptr<protocol::Value>",
  229. "pass_type": "std::unique_ptr<protocol::Value>",
  230. "to_raw_type": "%s.get()",
  231. "to_pass_type": "std::move(%s)",
  232. "to_rvalue": "std::move(%s)",
  233. "type": "std::unique_ptr<protocol::Value>",
  234. "raw_type": "protocol::Value",
  235. "raw_pass_type": "protocol::Value*",
  236. "raw_return_type": "protocol::Value*",
  237. }
  238. def create_string_type_definition():
  239. # pylint: disable=W0622
  240. return {
  241. "return_type": "String",
  242. "pass_type": "const String&",
  243. "to_pass_type": "%s",
  244. "to_raw_type": "%s",
  245. "to_rvalue": "%s",
  246. "type": "String",
  247. "raw_type": "String",
  248. "raw_pass_type": "const String&",
  249. "raw_return_type": "String",
  250. }
  251. def create_binary_type_definition():
  252. # pylint: disable=W0622
  253. return {
  254. "return_type": "Binary",
  255. "pass_type": "const Binary&",
  256. "to_pass_type": "%s",
  257. "to_raw_type": "%s",
  258. "to_rvalue": "%s",
  259. "type": "Binary",
  260. "raw_type": "Binary",
  261. "raw_pass_type": "const Binary&",
  262. "raw_return_type": "Binary",
  263. }
  264. def create_primitive_type_definition(type):
  265. # pylint: disable=W0622
  266. typedefs = {
  267. "number": "double",
  268. "integer": "int",
  269. "boolean": "bool"
  270. }
  271. defaults = {
  272. "number": "0",
  273. "integer": "0",
  274. "boolean": "false"
  275. }
  276. jsontypes = {
  277. "number": "TypeDouble",
  278. "integer": "TypeInteger",
  279. "boolean": "TypeBoolean",
  280. }
  281. return {
  282. "return_type": typedefs[type],
  283. "pass_type": typedefs[type],
  284. "to_pass_type": "%s",
  285. "to_raw_type": "%s",
  286. "to_rvalue": "%s",
  287. "type": typedefs[type],
  288. "raw_type": typedefs[type],
  289. "raw_pass_type": typedefs[type],
  290. "raw_return_type": typedefs[type],
  291. "default_value": defaults[type]
  292. }
  293. def wrap_array_definition(type):
  294. # pylint: disable=W0622
  295. return {
  296. "return_type": "std::unique_ptr<protocol::Array<%s>>" % type["raw_type"],
  297. "pass_type": "std::unique_ptr<protocol::Array<%s>>" % type["raw_type"],
  298. "to_raw_type": "%s.get()",
  299. "to_pass_type": "std::move(%s)",
  300. "to_rvalue": "std::move(%s)",
  301. "type": "std::unique_ptr<protocol::Array<%s>>" % type["raw_type"],
  302. "raw_type": "protocol::Array<%s>" % type["raw_type"],
  303. "raw_pass_type": "protocol::Array<%s>*" % type["raw_type"],
  304. "raw_return_type": "protocol::Array<%s>*" % type["raw_type"],
  305. "out_type": "protocol::Array<%s>&" % type["raw_type"],
  306. }
  307. class Protocol(object):
  308. def __init__(self, config):
  309. self.config = config
  310. self.json_api = {"domains": []}
  311. self.imported_domains = []
  312. self.exported_domains = []
  313. self.generate_domains = self.read_protocol_file(config.protocol.path)
  314. if config.protocol.options:
  315. self.generate_domains = [rule.domain for rule in config.protocol.options]
  316. self.exported_domains = [rule.domain for rule in config.protocol.options
  317. if hasattr(rule, "exported")]
  318. if config.imported:
  319. self.imported_domains = self.read_protocol_file(config.imported.path)
  320. if config.imported.options:
  321. self.imported_domains = [rule.domain
  322. for rule in config.imported.options]
  323. self.patch_full_qualified_refs()
  324. self.create_type_definitions()
  325. self.generate_used_types()
  326. def read_protocol_file(self, file_name):
  327. input_file = open(file_name, "r")
  328. parsed_json = pdl.loads(input_file.read(), file_name)
  329. input_file.close()
  330. version = '%s.%s' % (parsed_json["version"]["major"],
  331. parsed_json["version"]["minor"])
  332. domains = []
  333. for domain in parsed_json["domains"]:
  334. domains.append(domain["domain"])
  335. domain["version"] = version
  336. self.json_api["domains"] += parsed_json["domains"]
  337. return domains
  338. def patch_full_qualified_refs(self):
  339. def patch_full_qualified_refs_in_domain(json, domain_name):
  340. if isinstance(json, list):
  341. for item in json:
  342. patch_full_qualified_refs_in_domain(item, domain_name)
  343. if not isinstance(json, dict):
  344. return
  345. for key in json:
  346. if key == "type" and json[key] == "string":
  347. json[key] = domain_name + ".string"
  348. if key != "$ref":
  349. patch_full_qualified_refs_in_domain(json[key], domain_name)
  350. continue
  351. if json["$ref"].find(".") == -1:
  352. json["$ref"] = domain_name + "." + json["$ref"]
  353. return
  354. for domain in self.json_api["domains"]:
  355. patch_full_qualified_refs_in_domain(domain, domain["domain"])
  356. def all_references(self, json):
  357. refs = set()
  358. if isinstance(json, list):
  359. for item in json:
  360. refs |= self.all_references(item)
  361. if not isinstance(json, dict):
  362. return refs
  363. for key in json:
  364. if key != "$ref":
  365. refs |= self.all_references(json[key])
  366. else:
  367. refs.add(json["$ref"])
  368. return refs
  369. def generate_used_types(self):
  370. all_refs = set()
  371. for domain in self.json_api["domains"]:
  372. domain_name = domain["domain"]
  373. if "commands" in domain:
  374. for command in domain["commands"]:
  375. if self.generate_command(domain_name, command["name"]):
  376. all_refs |= self.all_references(command)
  377. if "events" in domain:
  378. for event in domain["events"]:
  379. if self.generate_event(domain_name, event["name"]):
  380. all_refs |= self.all_references(event)
  381. dependencies = self.generate_type_dependencies()
  382. queue = set(all_refs)
  383. while len(queue):
  384. ref = queue.pop()
  385. if ref in dependencies:
  386. queue |= dependencies[ref] - all_refs
  387. all_refs |= dependencies[ref]
  388. self.used_types = all_refs
  389. def generate_type_dependencies(self):
  390. dependencies = dict()
  391. domains_with_types = (x for x in self.json_api["domains"] if "types" in x)
  392. for domain in domains_with_types:
  393. domain_name = domain["domain"]
  394. for type in domain["types"]:
  395. related_types = self.all_references(type)
  396. if len(related_types):
  397. dependencies[domain_name + "." + type["id"]] = related_types
  398. return dependencies
  399. def create_type_definitions(self):
  400. imported_namespace = ""
  401. if self.config.imported:
  402. imported_namespace = "::".join(self.config.imported.namespace)
  403. self.type_definitions = {}
  404. self.type_definitions["number"] = create_primitive_type_definition("number")
  405. self.type_definitions["integer"] = create_primitive_type_definition("integer")
  406. self.type_definitions["boolean"] = create_primitive_type_definition("boolean")
  407. self.type_definitions["object"] = create_object_type_definition()
  408. self.type_definitions["any"] = create_any_type_definition()
  409. self.type_definitions["binary"] = create_binary_type_definition()
  410. for domain in self.json_api["domains"]:
  411. self.type_definitions[domain["domain"] + ".string"] = (
  412. create_string_type_definition())
  413. self.type_definitions[domain["domain"] + ".binary"] = (
  414. create_binary_type_definition())
  415. if not ("types" in domain):
  416. continue
  417. for type in domain["types"]:
  418. type_name = domain["domain"] + "." + type["id"]
  419. if type["type"] == "object" and domain["domain"] in self.imported_domains:
  420. self.type_definitions[type_name] = create_imported_type_definition(
  421. domain["domain"], type, imported_namespace)
  422. elif type["type"] == "object":
  423. self.type_definitions[type_name] = create_user_type_definition(
  424. domain["domain"], type)
  425. elif type["type"] == "array":
  426. self.type_definitions[type_name] = self.resolve_type(type)
  427. elif type["type"] == domain["domain"] + ".string":
  428. self.type_definitions[type_name] = create_string_type_definition()
  429. elif type["type"] == domain["domain"] + ".binary":
  430. self.type_definitions[type_name] = create_binary_type_definition()
  431. else:
  432. self.type_definitions[type_name] = create_primitive_type_definition(
  433. type["type"])
  434. def check_options(self, options, domain, name, include_attr, exclude_attr,
  435. default):
  436. for rule in options:
  437. if rule.domain != domain:
  438. continue
  439. if include_attr and hasattr(rule, include_attr):
  440. return name in getattr(rule, include_attr)
  441. if exclude_attr and hasattr(rule, exclude_attr):
  442. return name not in getattr(rule, exclude_attr)
  443. return default
  444. return False
  445. # ---- Begin of methods exposed to generator
  446. def type_definition(self, name):
  447. return self.type_definitions[name]
  448. def resolve_type(self, prop):
  449. if "$ref" in prop:
  450. return self.type_definitions[prop["$ref"]]
  451. if prop["type"] == "array":
  452. return wrap_array_definition(self.resolve_type(prop["items"]))
  453. return self.type_definitions[prop["type"]]
  454. def generate_command(self, domain, command):
  455. if not self.config.protocol.options:
  456. return domain in self.generate_domains
  457. return self.check_options(self.config.protocol.options, domain, command,
  458. "include", "exclude", True)
  459. def generate_event(self, domain, event):
  460. if not self.config.protocol.options:
  461. return domain in self.generate_domains
  462. return self.check_options(self.config.protocol.options, domain, event,
  463. "include_events", "exclude_events", True)
  464. def generate_type(self, domain, typename):
  465. return domain + "." + typename in self.used_types
  466. def is_async_command(self, domain, command):
  467. if not self.config.protocol.options:
  468. return False
  469. return self.check_options(self.config.protocol.options, domain, command,
  470. "async_", None, False)
  471. def is_exported(self, domain, name):
  472. if not self.config.protocol.options:
  473. return False
  474. return self.check_options(self.config.protocol.options, domain, name,
  475. "exported", None, False)
  476. def is_imported(self, domain, name):
  477. if not self.config.imported:
  478. return False
  479. if not self.config.imported.options:
  480. return domain in self.imported_domains
  481. return self.check_options(self.config.imported.options, domain, name,
  482. "imported", None, False)
  483. def is_exported_domain(self, domain):
  484. return domain in self.exported_domains
  485. def generate_disable(self, domain):
  486. if "commands" not in domain:
  487. return True
  488. for command in domain["commands"]:
  489. if command["name"] == "disable" and self.generate_command(
  490. domain["domain"], "disable"):
  491. return False
  492. return True
  493. def is_imported_dependency(self, domain):
  494. return domain in self.generate_domains or domain in self.imported_domains
  495. def main():
  496. jinja_dir, config_file, config = read_config()
  497. protocol = Protocol(config)
  498. if not config.exported and len(protocol.exported_domains):
  499. sys.stderr.write(("Domains [%s] are exported, but config is missing export "
  500. "entry\n\n") % ", ".join(protocol.exported_domains))
  501. exit(1)
  502. if not os.path.exists(config.protocol.output):
  503. os.mkdir(config.protocol.output)
  504. if len(protocol.exported_domains) and not os.path.exists(
  505. config.exported.output):
  506. os.mkdir(config.exported.output)
  507. jinja_env = initialize_jinja_env(jinja_dir, config.protocol.output, config)
  508. inputs = []
  509. inputs.append(__file__)
  510. inputs.append(config_file)
  511. inputs.append(config.protocol.path)
  512. if config.imported:
  513. inputs.append(config.imported.path)
  514. templates_dir = os.path.join(module_path, "templates")
  515. inputs.append(os.path.join(templates_dir, "TypeBuilder_h.template"))
  516. inputs.append(os.path.join(templates_dir, "TypeBuilder_cpp.template"))
  517. inputs.append(os.path.join(templates_dir, "Exported_h.template"))
  518. inputs.append(os.path.join(templates_dir, "Imported_h.template"))
  519. h_template = jinja_env.get_template("templates/TypeBuilder_h.template")
  520. cpp_template = jinja_env.get_template("templates/TypeBuilder_cpp.template")
  521. exported_template = jinja_env.get_template("templates/Exported_h.template")
  522. imported_template = jinja_env.get_template("templates/Imported_h.template")
  523. outputs = dict()
  524. for domain in protocol.json_api["domains"]:
  525. class_name = domain["domain"]
  526. file_name = config.protocol.file_name_prefix + class_name
  527. template_context = {
  528. "protocol": protocol,
  529. "config": config,
  530. "domain": domain,
  531. "join_arrays": join_arrays,
  532. "format_include": functools.partial(format_include, config),
  533. "format_domain_include": functools.partial(format_domain_include, config),
  534. }
  535. if domain["domain"] in protocol.generate_domains:
  536. outputs[os.path.join(config.protocol.output, to_file_name(
  537. config, file_name + ".h"))] = h_template.render(template_context)
  538. outputs[os.path.join(config.protocol.output, to_file_name(
  539. config, file_name + ".cpp"))] = cpp_template.render(template_context)
  540. if domain["domain"] in protocol.exported_domains:
  541. outputs[os.path.join(config.exported.output, to_file_name(
  542. config, file_name + ".h"))] = exported_template.render(
  543. template_context)
  544. if domain["domain"] in protocol.imported_domains:
  545. outputs[os.path.join(config.protocol.output, to_file_name(
  546. config, file_name + ".h"))] = imported_template.render(
  547. template_context)
  548. if config.lib:
  549. template_context = {
  550. "config": config,
  551. "format_include": functools.partial(format_include, config),
  552. }
  553. lib_templates_dir = os.path.join(module_path, "lib")
  554. # Note these should be sorted in the right order.
  555. # TODO(dgozman): sort them programmatically based on commented includes.
  556. forward_h_templates = [
  557. "Forward_h.template",
  558. ]
  559. protocol_h_templates = []
  560. protocol_cpp_templates = []
  561. if not config.use_embedder_types:
  562. protocol_h_templates += [
  563. "Values_h.template",
  564. "Object_h.template",
  565. "ValueConversions_h.template",
  566. ]
  567. protocol_cpp_templates += [
  568. "Protocol_cpp.template",
  569. "Values_cpp.template",
  570. "Object_cpp.template",
  571. "ValueConversions_cpp.template",
  572. ]
  573. else:
  574. protocol_h_templates += [
  575. "Forward_h.template",
  576. ]
  577. def generate_lib_file(file_name, template_files):
  578. parts = []
  579. for template_file in template_files:
  580. inputs.append(os.path.join(lib_templates_dir, template_file))
  581. template = jinja_env.get_template("lib/" + template_file)
  582. parts.append(template.render(template_context))
  583. outputs[file_name] = "\n\n".join(parts)
  584. generate_lib_file(os.path.join(config.lib.output, to_file_name(
  585. config, "Forward.h")), forward_h_templates)
  586. generate_lib_file(os.path.join(config.lib.output, to_file_name(
  587. config, "Protocol.h")), protocol_h_templates)
  588. if not config.use_embedder_types:
  589. generate_lib_file(os.path.join(config.lib.output, to_file_name(
  590. config, "Protocol.cpp")), protocol_cpp_templates)
  591. # Make gyp / make generatos happy, otherwise make rebuilds world.
  592. inputs_ts = max(map(os.path.getmtime, inputs))
  593. up_to_date = True
  594. for output_file in outputs.keys():
  595. if (not os.path.exists(output_file)
  596. or os.path.getmtime(output_file) < inputs_ts):
  597. up_to_date = False
  598. break
  599. if up_to_date:
  600. sys.exit()
  601. for file_name, content in outputs.items():
  602. # Remove output file first to account for potential case changes.
  603. try:
  604. os.remove(file_name)
  605. except OSError:
  606. pass
  607. out_file = open(file_name, "w")
  608. out_file.write(content)
  609. out_file.close()
  610. if __name__ == "__main__":
  611. main()