[DevTools] Introduce config file for inspector_protocol.

Drive-by: fixing small issues in generator.

BUG=637032

Review-Url: https://codereview.chromium.org/2240663003
Cr-Original-Commit-Position: refs/heads/master@{#411898}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 299805a3efd15f7464d2a768051b9f107adc09a4
diff --git a/CodeGenerator.py b/CodeGenerator.py
index 0b09c8e..5743e16 100644
--- a/CodeGenerator.py
+++ b/CodeGenerator.py
@@ -48,45 +48,19 @@
 import jinja2
 
 cmdline_parser = optparse.OptionParser()
-cmdline_parser.add_option("--protocol")
-cmdline_parser.add_option("--include")
-cmdline_parser.add_option("--include_package")
-cmdline_parser.add_option("--string_type")
-cmdline_parser.add_option("--export_macro")
-cmdline_parser.add_option("--output_dir")
-cmdline_parser.add_option("--output_package")
-cmdline_parser.add_option("--exported_dir")
-cmdline_parser.add_option("--exported_package")
+cmdline_parser.add_option("--output_base")
+cmdline_parser.add_option("--config")
+
 
 try:
     arg_options, arg_values = cmdline_parser.parse_args()
-    protocol_file = arg_options.protocol
-    if not protocol_file:
-        raise Exception("Protocol directory must be specified")
-    include_file = arg_options.include
-    include_package = arg_options.include_package
-    if include_file and not include_package:
-        raise Exception("Include package must be specified when using include file")
-    if include_package and not include_file:
-        raise Exception("Include file must be specified when using include package")
-    output_dirname = arg_options.output_dir
-    if not output_dirname:
-        raise Exception("Output directory must be specified")
-    output_package = arg_options.output_package
-    if not output_package:
-        raise Exception("Output package must be specified")
-    exported_dirname = arg_options.exported_dir
-    if not exported_dirname:
-        exported_dirname = os.path.join(output_dirname, "exported")
-    exported_package = arg_options.exported_package
-    if not exported_package:
-        exported_package = os.path.join(output_package, "exported")
-    string_type = arg_options.string_type
-    if not string_type:
-        raise Exception("String type must be specified")
-    export_macro = arg_options.export_macro
-    if not export_macro:
-        raise Exception("Export macro must be specified")
+    output_base = arg_options.output_base
+    if not output_base:
+        raise Exception("Base output directory must be specified")
+    config_file = arg_options.config
+    if not config_file:
+        raise Exception("Config file name must be specified")
+    config_dir = os.path.dirname(config_file)
 except Exception:
     # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
     exc = sys.exc_info()[1]
@@ -94,9 +68,56 @@
     exit(1)
 
 
-input_file = open(protocol_file, "r")
-json_string = input_file.read()
-parsed_json = json.loads(json_string)
+try:
+    config_json_string = open(config_file, "r").read()
+    config = json.loads(config_json_string)
+
+    protocol_file = config["protocol"]["path"]
+    if not protocol_file:
+        raise Exception("Config is missing protocol.path")
+    protocol_file = os.path.join(config_dir, protocol_file)
+    output_dirname = config["protocol"]["output"]
+    if not output_dirname:
+        raise Exception("Config is missing protocol.output")
+    output_dirname = os.path.join(output_base, output_dirname)
+    output_package = config["protocol"]["package"]
+    if not output_package:
+        raise Exception("Config is missing protocol.package")
+
+    importing = False
+    if "import" in config:
+        importing = True
+        imported_file = config["import"]["path"]
+        if not imported_file:
+            raise Exception("Config is missing import.path")
+        imported_file = os.path.join(config_dir, imported_file)
+        imported_package = config["import"]["package"]
+        if not imported_package:
+            raise Exception("Config is missing import.package")
+
+    exporting = False
+    if "export" in config:
+        exporting = True
+        exported_dirname = config["export"]["output"]
+        if not exported_dirname:
+            raise Exception("Config is missing export.output")
+        exported_dirname = os.path.join(output_base, exported_dirname)
+        exported_package = config["export"]["package"]
+        if not exported_package:
+            raise Exception("Config is missing export.package")
+
+    string_type = config["string"]["class_name"]
+    if not string_type:
+        raise Exception("Config is missing string.class_name")
+
+    export_macro = config["export_macro"]
+    if not export_macro:
+        raise Exception("Config is missing export_macro")
+except Exception:
+    # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
+    exc = sys.exc_info()[1]
+    sys.stderr.write("Failed to parse config file: %s\n\n" % exc)
+    exit(1)
 
 
 # Make gyp / make generatos happy, otherwise make rebuilds world.
@@ -107,16 +128,19 @@
         os.path.getmtime(os.path.join(templates_dir, "TypeBuilder_cpp.template")),
         os.path.getmtime(os.path.join(templates_dir, "Exported_h.template")),
         os.path.getmtime(os.path.join(templates_dir, "Imported_h.template")),
+        os.path.getmtime(config_file),
         os.path.getmtime(protocol_file))
+    if importing:
+        template_ts = max(template_ts, os.path.getmtime(imported_file))
 
-    for domain in parsed_json["domains"]:
+    for domain in json_api["domains"]:
         name = domain["domain"]
         paths = []
         if name in generate_domains:
             paths = [os.path.join(output_dirname, name + ".h"), os.path.join(output_dirname, name + ".cpp")]
             if domain["has_exports"]:
                 paths.append(os.path.join(exported_dirname, name + ".h"))
-        if name in include_domains and domain["has_exports"]:
+        if name in imported_domains and domain["has_exports"]:
             paths = [os.path.join(output_dirname, name + '.h')]
         for path in paths:
             if not os.path.exists(path):
@@ -193,11 +217,16 @@
 
     json_api["has_exports"] = False
     for domain_json in json_api["domains"]:
+        domain_name = domain_json["domain"]
         domain_json["has_exports"] = calculate_exports_in_json(domain_json)
-        json_api["has_exports"] = json_api["has_exports"] or domain_json["has_exports"]
+        if domain_json["has_exports"] and domain_name in generate_domains:
+            if not exporting:
+                sys.stderr.write("Domain %s is exported, but config is missing export entry\n\n" % domain_name)
+                exit(1)
+            json_api["has_exports"] = True
 
 
-def create_include_type_definition(domain_name, type):
+def create_imported_type_definition(domain_name, type):
     # pylint: disable=W0622
     return {
         "return_type": "std::unique_ptr<protocol::%s::API::%s>" % (domain_name, type["id"]),
@@ -335,8 +364,8 @@
             continue
         for type in domain["types"]:
             type_name = domain["domain"] + "." + type["id"]
-            if type["type"] == "object" and domain["domain"] in include_domains:
-                type_definitions[type_name] = create_include_type_definition(domain["domain"], type)
+            if type["type"] == "object" and domain["domain"] in imported_domains:
+                type_definitions[type_name] = create_imported_type_definition(domain["domain"], type)
             elif type["type"] == "object":
                 type_definitions[type_name] = create_user_type_definition(domain["domain"], type)
             elif type["type"] == "array":
@@ -375,6 +404,12 @@
     return False
 
 
+def generate_with_context(template_context, template, file_name):
+    out_file = output_file(file_name)
+    out_file.write(template.render(template_context))
+    out_file.close()
+
+
 def generate(domain_object, template, file_name):
     template_context = {
         "domain": domain_object,
@@ -383,31 +418,29 @@
         "type_definition": type_definition,
         "has_disable": has_disable,
         "export_macro": export_macro,
-        "output_package": output_package,
-        "exported_package": exported_package,
-        "include_package": include_package
+        "output_package": output_package
     }
-    out_file = output_file(file_name)
-    out_file.write(template.render(template_context))
-    out_file.close()
+    if exporting:
+        template_context["exported_package"] = exported_package
+    if importing:
+        template_context["imported_package"] = imported_package
+    generate_with_context(template_context, template, file_name)
 
 
-generate_domains = []
-include_domains = []
-json_api = {}
-json_api["domains"] = parsed_json["domains"]
-
-for domain in parsed_json["domains"]:
-    generate_domains.append(domain["domain"])
-
-if include_file:
-    input_file = open(include_file, "r")
+def read_protocol_file(file_name, all_domains):
+    input_file = open(file_name, "r")
     json_string = input_file.read()
     parsed_json = json.loads(json_string)
+    domains = []
     for domain in parsed_json["domains"]:
-        include_domains.append(domain["domain"])
-    json_api["domains"] += parsed_json["domains"]
+        domains.append(domain["domain"])
+    all_domains["domains"] += parsed_json["domains"]
+    return domains
 
+
+json_api = {"domains": []}
+generate_domains = read_protocol_file(protocol_file, json_api)
+imported_domains = read_protocol_file(imported_file, json_api) if importing else []
 patch_full_qualified_refs()
 calculate_exports()
 create_type_definitions()
@@ -428,9 +461,9 @@
 for domain in json_api["domains"]:
     class_name = domain["domain"]
     if domain["domain"] in generate_domains:
-        generate(domain, h_template, output_dirname + "/" + class_name + ".h")
-        generate(domain, cpp_template, output_dirname + "/" + class_name + ".cpp")
+        generate(domain, h_template, os.path.join(output_dirname, class_name + ".h"))
+        generate(domain, cpp_template, os.path.join(output_dirname, class_name + ".cpp"))
         if domain["has_exports"]:
-            generate(domain, exported_template, exported_dirname + "/" + class_name + ".h")
-    if domain["domain"] in include_domains and domain["has_exports"]:
-        generate(domain, imported_template, output_dirname + "/" + class_name + ".h")
+            generate(domain, exported_template, os.path.join(exported_dirname, class_name + ".h"))
+    if domain["domain"] in imported_domains and domain["has_exports"]:
+        generate(domain, imported_template, os.path.join(output_dirname, class_name + ".h"))
diff --git a/Imported_h.template b/Imported_h.template
index a9abdad..3bfaef3 100644
--- a/Imported_h.template
+++ b/Imported_h.template
@@ -10,7 +10,7 @@
 #include "platform/inspector_protocol/ErrorSupport.h"
 #include "platform/inspector_protocol/ValueConversions.h"
 #include "platform/inspector_protocol/Values.h"
-#include "{{include_package}}/{{domain.domain}}.h"
+#include "{{imported_package}}/{{domain.domain}}.h"
 
 namespace blink {
 namespace protocol {
diff --git a/sample_config.json b/sample_config.json
new file mode 100644
index 0000000..12d7e8b
--- /dev/null
+++ b/sample_config.json
@@ -0,0 +1,23 @@
+{
+    "protocol": {
+        "path": "protocol/sample_protocol.json",
+        "package": "include/generated/files/like/this",
+        "output": "place/generated/files/here"
+    },
+
+    "import": {
+        "path": "../relative/path/imported_protocol.json",
+        "package": "include/imported/files/like/this"
+    },
+
+    "export": {
+        "package": "include/exported/files/like/this",
+        "output": "place/exported/files/here"
+    },
+
+    "string": {
+        "class_name": "String16"
+    },
+
+    "export_macro": "PLATFORM_EXPORT"
+}