[DevTools] Prepare inspector_protocol build to move.

- Do not list inspector_protocol files anywhere outside;
- Separate concatenation from compatibility check;
- Improve files layout;
- Pass path to jinja module to CodeGenerator.

BUG=637032

Review-Url: https://codereview.chromium.org/2282283002
Cr-Original-Commit-Position: refs/heads/master@{#415137}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: a73483cd5dd6d569d079e7ea43210f8482d12552
diff --git a/CheckProtocolCompatibility.py b/CheckProtocolCompatibility.py
new file mode 100755
index 0000000..dd9acad
--- /dev/null
+++ b/CheckProtocolCompatibility.py
@@ -0,0 +1,479 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Inspector protocol validator.
+#
+# Tests that subsequent protocol changes are not breaking backwards compatibility.
+# Following violations are reported:
+#
+#   - Domain has been removed
+#   - Command has been removed
+#   - Required command parameter was added or changed from optional
+#   - Required response parameter was removed or changed to optional
+#   - Event has been removed
+#   - Required event parameter was removed or changed to optional
+#   - Parameter type has changed.
+#
+# For the parameters with composite types the above checks are also applied
+# recursively to every property of the type.
+#
+# Adding --show_changes to the command line prints out a list of valid public API changes.
+
+import copy
+import os.path
+import optparse
+import sys
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+
+def list_to_map(items, key):
+    result = {}
+    for item in items:
+        if "experimental" not in item and "hidden" not in item:
+            result[item[key]] = item
+    return result
+
+
+def named_list_to_map(container, name, key):
+    if name in container:
+        return list_to_map(container[name], key)
+    return {}
+
+
+def removed(reverse):
+    if reverse:
+        return "added"
+    return "removed"
+
+
+def required(reverse):
+    if reverse:
+        return "optional"
+    return "required"
+
+
+def compare_schemas(d_1, d_2, reverse):
+    errors = []
+    domains_1 = copy.deepcopy(d_1)
+    domains_2 = copy.deepcopy(d_2)
+    types_1 = normalize_types_in_schema(domains_1)
+    types_2 = normalize_types_in_schema(domains_2)
+
+    domains_by_name_1 = list_to_map(domains_1, "domain")
+    domains_by_name_2 = list_to_map(domains_2, "domain")
+
+    for name in domains_by_name_1:
+        domain_1 = domains_by_name_1[name]
+        if name not in domains_by_name_2:
+            errors.append("%s: domain has been %s" % (name, removed(reverse)))
+            continue
+        compare_domains(domain_1, domains_by_name_2[name], types_1, types_2, errors, reverse)
+    return errors
+
+
+def compare_domains(domain_1, domain_2, types_map_1, types_map_2, errors, reverse):
+    domain_name = domain_1["domain"]
+    commands_1 = named_list_to_map(domain_1, "commands", "name")
+    commands_2 = named_list_to_map(domain_2, "commands", "name")
+    for name in commands_1:
+        command_1 = commands_1[name]
+        if name not in commands_2:
+            errors.append("%s.%s: command has been %s" % (domain_1["domain"], name, removed(reverse)))
+            continue
+        compare_commands(domain_name, command_1, commands_2[name], types_map_1, types_map_2, errors, reverse)
+
+    events_1 = named_list_to_map(domain_1, "events", "name")
+    events_2 = named_list_to_map(domain_2, "events", "name")
+    for name in events_1:
+        event_1 = events_1[name]
+        if name not in events_2:
+            errors.append("%s.%s: event has been %s" % (domain_1["domain"], name, removed(reverse)))
+            continue
+        compare_events(domain_name, event_1, events_2[name], types_map_1, types_map_2, errors, reverse)
+
+
+def compare_commands(domain_name, command_1, command_2, types_map_1, types_map_2, errors, reverse):
+    context = domain_name + "." + command_1["name"]
+
+    params_1 = named_list_to_map(command_1, "parameters", "name")
+    params_2 = named_list_to_map(command_2, "parameters", "name")
+    # Note the reversed order: we allow removing but forbid adding parameters.
+    compare_params_list(context, "parameter", params_2, params_1, types_map_2, types_map_1, 0, errors, not reverse)
+
+    returns_1 = named_list_to_map(command_1, "returns", "name")
+    returns_2 = named_list_to_map(command_2, "returns", "name")
+    compare_params_list(context, "response parameter", returns_1, returns_2, types_map_1, types_map_2, 0, errors, reverse)
+
+
+def compare_events(domain_name, event_1, event_2, types_map_1, types_map_2, errors, reverse):
+    context = domain_name + "." + event_1["name"]
+    params_1 = named_list_to_map(event_1, "parameters", "name")
+    params_2 = named_list_to_map(event_2, "parameters", "name")
+    compare_params_list(context, "parameter", params_1, params_2, types_map_1, types_map_2, 0, errors, reverse)
+
+
+def compare_params_list(context, kind, params_1, params_2, types_map_1, types_map_2, depth, errors, reverse):
+    for name in params_1:
+        param_1 = params_1[name]
+        if name not in params_2:
+            if "optional" not in param_1:
+                errors.append("%s.%s: required %s has been %s" % (context, name, kind, removed(reverse)))
+            continue
+
+        param_2 = params_2[name]
+        if param_2 and "optional" in param_2 and "optional" not in param_1:
+            errors.append("%s.%s: %s %s is now %s" % (context, name, required(reverse), kind, required(not reverse)))
+            continue
+        type_1 = extract_type(param_1, types_map_1, errors)
+        type_2 = extract_type(param_2, types_map_2, errors)
+        compare_types(context + "." + name, kind, type_1, type_2, types_map_1, types_map_2, depth, errors, reverse)
+
+
+def compare_types(context, kind, type_1, type_2, types_map_1, types_map_2, depth, errors, reverse):
+    if depth > 10:
+        return
+
+    base_type_1 = type_1["type"]
+    base_type_2 = type_2["type"]
+
+    if base_type_1 != base_type_2:
+        errors.append("%s: %s base type mismatch, '%s' vs '%s'" % (context, kind, base_type_1, base_type_2))
+    elif base_type_1 == "object":
+        params_1 = named_list_to_map(type_1, "properties", "name")
+        params_2 = named_list_to_map(type_2, "properties", "name")
+        # If both parameters have the same named type use it in the context.
+        if "id" in type_1 and "id" in type_2 and type_1["id"] == type_2["id"]:
+            type_name = type_1["id"]
+        else:
+            type_name = "<object>"
+        context += " %s->%s" % (kind, type_name)
+        compare_params_list(context, "property", params_1, params_2, types_map_1, types_map_2, depth + 1, errors, reverse)
+    elif base_type_1 == "array":
+        item_type_1 = extract_type(type_1["items"], types_map_1, errors)
+        item_type_2 = extract_type(type_2["items"], types_map_2, errors)
+        compare_types(context, kind, item_type_1, item_type_2, types_map_1, types_map_2, depth + 1, errors, reverse)
+
+
+def extract_type(typed_object, types_map, errors):
+    if "type" in typed_object:
+        result = {"id": "<transient>", "type": typed_object["type"]}
+        if typed_object["type"] == "object":
+            result["properties"] = []
+        elif typed_object["type"] == "array":
+            result["items"] = typed_object["items"]
+        return result
+    elif "$ref" in typed_object:
+        ref = typed_object["$ref"]
+        if ref not in types_map:
+            errors.append("Can not resolve type: %s" % ref)
+            types_map[ref] = {"id": "<transient>", "type": "object"}
+        return types_map[ref]
+
+
+def normalize_types_in_schema(domains):
+    types = {}
+    for domain in domains:
+        domain_name = domain["domain"]
+        normalize_types(domain, domain_name, types)
+    return types
+
+
+def normalize_types(obj, domain_name, types):
+    if isinstance(obj, list):
+        for item in obj:
+            normalize_types(item, domain_name, types)
+    elif isinstance(obj, dict):
+        for key, value in obj.items():
+            if key == "$ref" and value.find(".") == -1:
+                obj[key] = "%s.%s" % (domain_name, value)
+            elif key == "id":
+                obj[key] = "%s.%s" % (domain_name, value)
+                types[obj[key]] = obj
+            else:
+                normalize_types(value, domain_name, types)
+
+
+def load_schema(file_name, domains):
+    # pylint: disable=W0613
+    if not os.path.isfile(file_name):
+        return
+    input_file = open(file_name, "r")
+    json_string = input_file.read()
+    parsed_json = json.loads(json_string)
+    domains += parsed_json["domains"]
+    return parsed_json["version"]
+
+
+def self_test():
+    def create_test_schema_1():
+        return [
+            {
+                "domain": "Network",
+                "types": [
+                    {
+                        "id": "LoaderId",
+                        "type": "string"
+                    },
+                    {
+                        "id": "Headers",
+                        "type": "object"
+                    },
+                    {
+                        "id": "Request",
+                        "type": "object",
+                        "properties": [
+                            {"name": "url", "type": "string"},
+                            {"name": "method", "type": "string"},
+                            {"name": "headers", "$ref": "Headers"},
+                            {"name": "becameOptionalField", "type": "string"},
+                            {"name": "removedField", "type": "string"},
+                        ]
+                    }
+                ],
+                "commands": [
+                    {
+                        "name": "removedCommand",
+                    },
+                    {
+                        "name": "setExtraHTTPHeaders",
+                        "parameters": [
+                            {"name": "headers", "$ref": "Headers"},
+                            {"name": "mismatched", "type": "string"},
+                            {"name": "becameOptional", "$ref": "Headers"},
+                            {"name": "removedRequired", "$ref": "Headers"},
+                            {"name": "becameRequired", "$ref": "Headers", "optional": True},
+                            {"name": "removedOptional", "$ref": "Headers", "optional": True},
+                        ],
+                        "returns": [
+                            {"name": "mimeType", "type": "string"},
+                            {"name": "becameOptional", "type": "string"},
+                            {"name": "removedRequired", "type": "string"},
+                            {"name": "becameRequired", "type": "string", "optional": True},
+                            {"name": "removedOptional", "type": "string", "optional": True},
+                        ]
+                    }
+                ],
+                "events": [
+                    {
+                        "name": "requestWillBeSent",
+                        "parameters": [
+                            {"name": "frameId", "type": "string", "experimental": True},
+                            {"name": "request", "$ref": "Request"},
+                            {"name": "becameOptional", "type": "string"},
+                            {"name": "removedRequired", "type": "string"},
+                            {"name": "becameRequired", "type": "string", "optional": True},
+                            {"name": "removedOptional", "type": "string", "optional": True},
+                        ]
+                    },
+                    {
+                        "name": "removedEvent",
+                        "parameters": [
+                            {"name": "errorText", "type": "string"},
+                            {"name": "canceled", "type": "boolean", "optional": True}
+                        ]
+                    }
+                ]
+            },
+            {
+                "domain":  "removedDomain"
+            }
+        ]
+
+    def create_test_schema_2():
+        return [
+            {
+                "domain": "Network",
+                "types": [
+                    {
+                        "id": "LoaderId",
+                        "type": "string"
+                    },
+                    {
+                        "id": "Request",
+                        "type": "object",
+                        "properties": [
+                            {"name": "url", "type": "string"},
+                            {"name": "method", "type": "string"},
+                            {"name": "headers", "type": "object"},
+                            {"name": "becameOptionalField", "type": "string", "optional": True},
+                        ]
+                    }
+                ],
+                "commands": [
+                    {
+                        "name": "addedCommand",
+                    },
+                    {
+                        "name": "setExtraHTTPHeaders",
+                        "parameters": [
+                            {"name": "headers", "type": "object"},
+                            {"name": "mismatched", "type": "object"},
+                            {"name": "becameOptional", "type": "object", "optional": True},
+                            {"name": "addedRequired", "type": "object"},
+                            {"name": "becameRequired", "type": "object"},
+                            {"name": "addedOptional", "type": "object", "optional": True},
+                        ],
+                        "returns": [
+                            {"name": "mimeType", "type": "string"},
+                            {"name": "becameOptional", "type": "string", "optional": True},
+                            {"name": "addedRequired", "type": "string"},
+                            {"name": "becameRequired", "type": "string"},
+                            {"name": "addedOptional", "type": "string", "optional": True},
+                        ]
+                    }
+                ],
+                "events": [
+                    {
+                        "name": "requestWillBeSent",
+                        "parameters": [
+                            {"name": "request", "$ref": "Request"},
+                            {"name": "becameOptional", "type": "string", "optional": True},
+                            {"name": "addedRequired", "type": "string"},
+                            {"name": "becameRequired", "type": "string"},
+                            {"name": "addedOptional", "type": "string", "optional": True},
+                        ]
+                    },
+                    {
+                        "name": "addedEvent"
+                    }
+                ]
+            },
+            {
+                "domain": "addedDomain"
+            }
+        ]
+
+    expected_errors = [
+        "removedDomain: domain has been removed",
+        "Network.removedCommand: command has been removed",
+        "Network.removedEvent: event has been removed",
+        "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, 'object' vs 'string'",
+        "Network.setExtraHTTPHeaders.addedRequired: required parameter has been added",
+        "Network.setExtraHTTPHeaders.becameRequired: optional parameter is now required",
+        "Network.setExtraHTTPHeaders.removedRequired: required response parameter has been removed",
+        "Network.setExtraHTTPHeaders.becameOptional: required response parameter is now optional",
+        "Network.requestWillBeSent.removedRequired: required parameter has been removed",
+        "Network.requestWillBeSent.becameOptional: required parameter is now optional",
+        "Network.requestWillBeSent.request parameter->Network.Request.removedField: required property has been removed",
+        "Network.requestWillBeSent.request parameter->Network.Request.becameOptionalField: required property is now optional",
+    ]
+
+    expected_errors_reverse = [
+        "addedDomain: domain has been added",
+        "Network.addedEvent: event has been added",
+        "Network.addedCommand: command has been added",
+        "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, 'string' vs 'object'",
+        "Network.setExtraHTTPHeaders.removedRequired: required parameter has been removed",
+        "Network.setExtraHTTPHeaders.becameOptional: required parameter is now optional",
+        "Network.setExtraHTTPHeaders.addedRequired: required response parameter has been added",
+        "Network.setExtraHTTPHeaders.becameRequired: optional response parameter is now required",
+        "Network.requestWillBeSent.becameRequired: optional parameter is now required",
+        "Network.requestWillBeSent.addedRequired: required parameter has been added",
+    ]
+
+    def is_subset(subset, superset, message):
+        for i in range(len(subset)):
+            if subset[i] not in superset:
+                sys.stderr.write("%s error: %s\n" % (message, subset[i]))
+                return False
+        return True
+
+    def errors_match(expected, actual):
+        return (is_subset(actual, expected, "Unexpected") and
+                is_subset(expected, actual, "Missing"))
+
+    return (errors_match(expected_errors,
+                         compare_schemas(create_test_schema_1(), create_test_schema_2(), False)) and
+            errors_match(expected_errors_reverse,
+                         compare_schemas(create_test_schema_2(), create_test_schema_1(), True)))
+
+
+def load_domains_and_baselines(file_name, domains, baseline_domains):
+    version = load_schema(os.path.normpath(file_name), domains)
+    suffix = "-%s.%s.json" % (version["major"], version["minor"])
+    baseline_file = file_name.replace(".json", suffix)
+    load_schema(os.path.normpath(baseline_file), baseline_domains)
+    return version
+
+
+def main():
+    if not self_test():
+        sys.stderr.write("Self-test failed")
+        return 1
+
+    cmdline_parser = optparse.OptionParser()
+    cmdline_parser.add_option("--show_changes")
+    cmdline_parser.add_option("--expected_errors")
+    cmdline_parser.add_option("--stamp")
+    arg_options, arg_values = cmdline_parser.parse_args()
+
+    if len(arg_values) < 1:
+        sys.stderr.write("Usage: %s [--show_changes] <protocol-1> [, <protocol-2>...]\n" % sys.argv[0])
+        return 1
+
+    domains = []
+    baseline_domains = []
+    version = load_domains_and_baselines(arg_values[0], domains, baseline_domains)
+    for dependency in arg_values[1:]:
+        load_domains_and_baselines(dependency, domains, baseline_domains)
+
+    expected_errors = []
+    if arg_options.expected_errors:
+        expected_errors_file = open(arg_options.expected_errors, "r")
+        expected_errors = json.loads(expected_errors_file.read())["errors"]
+        expected_errors_file.close()
+
+    errors = compare_schemas(baseline_domains, domains, False)
+    unexpected_errors = []
+    for i in range(len(errors)):
+        if errors[i] not in expected_errors:
+            unexpected_errors.append(errors[i])
+    if len(unexpected_errors) > 0:
+        sys.stderr.write("  Compatibility checks FAILED\n")
+        for error in unexpected_errors:
+            sys.stderr.write("    %s\n" % error)
+        return 1
+
+    if arg_options.show_changes:
+        changes = compare_schemas(domains, baseline_domains, True)
+        if len(changes) > 0:
+            print "  Public changes since %s:" % version
+            for change in changes:
+                print "    %s" % change
+
+    if arg_options.stamp:
+        with open(arg_options.stamp, 'a') as _:
+            pass
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/CodeGenerator.py b/CodeGenerator.py
index 9fd74a7..48c422a 100644
--- a/CodeGenerator.py
+++ b/CodeGenerator.py
@@ -21,34 +21,6 @@
 # is regenerated, which causes a race condition and breaks concurrent build,
 # since some compile processes will try to read the partially written cache.
 module_path, module_filename = os.path.split(os.path.realpath(__file__))
-templates_dir = module_path
-
-# In Blink, jinja2 is in chromium's third_party directory.
-# Insert at 1 so at front to override system libraries, and
-# after path[0] == invoking script dir
-blink_third_party_dir = os.path.normpath(os.path.join(
-    module_path, os.pardir, os.pardir, os.pardir, os.pardir, os.pardir,
-    "third_party"))
-if os.path.isdir(blink_third_party_dir):
-    sys.path.insert(1, blink_third_party_dir)
-
-# In V8, it is in third_party folder
-v8_third_party_dir = os.path.normpath(os.path.join(
-    module_path, os.pardir, os.pardir, "third_party"))
-
-if os.path.isdir(v8_third_party_dir):
-    sys.path.insert(1, v8_third_party_dir)
-
-# In Node, it is in deps folder
-deps_dir = os.path.normpath(os.path.join(
-    module_path, os.pardir, os.pardir, os.pardir, os.pardir, "third_party"))
-
-if os.path.isdir(deps_dir):
-    sys.path.insert(1, os.path.join(deps_dir, "jinja2"))
-    sys.path.insert(1, os.path.join(deps_dir, "markupsafe"))
-
-import jinja2
-
 
 def read_config():
     # pylint: disable=W0703
@@ -63,8 +35,12 @@
     try:
         cmdline_parser = optparse.OptionParser()
         cmdline_parser.add_option("--output_base")
+        cmdline_parser.add_option("--jinja_dir")
         cmdline_parser.add_option("--config")
         arg_options, _ = cmdline_parser.parse_args()
+        jinja_dir = arg_options.jinja_dir
+        if not jinja_dir:
+            raise Exception("jinja directory must be specified")
         output_base = arg_options.output_base
         if not output_base:
             raise Exception("Base output directory must be specified")
@@ -89,7 +65,7 @@
                 keys.append(optional)
                 values.append(False)
         config_json_file.close()
-        return (config_file, collections.namedtuple('X', keys)(*values))
+        return (jinja_dir, config_file, collections.namedtuple('X', keys)(*values))
     except Exception:
         # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
         exc = sys.exc_info()[1]
@@ -109,9 +85,13 @@
     return prefix + "".join(to_title_case(x) or "-" for x in word.split("-"))
 
 
-def initialize_jinja_env(cache_dir):
+def initialize_jinja_env(jinja_dir, cache_dir):
+    # pylint: disable=F0401
+    sys.path.insert(1, os.path.abspath(jinja_dir))
+    import jinja2
+
     jinja_env = jinja2.Environment(
-        loader=jinja2.FileSystemLoader(templates_dir),
+        loader=jinja2.FileSystemLoader(module_path),
         # Bytecode cache is not concurrency-safe unless pre-cached:
         # if pre-cached this is read-only, but writing creates a race condition.
         bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir),
@@ -362,7 +342,7 @@
 
 
 def main():
-    config_file, config = read_config()
+    jinja_dir, config_file, config = read_config()
 
     protocol = Protocol()
     protocol.json_api = {"domains": []}
@@ -382,7 +362,7 @@
         os.mkdir(config.protocol.output)
     if protocol.json_api["has_exports"] and not os.path.exists(config.exported.output):
         os.mkdir(config.exported.output)
-    jinja_env = initialize_jinja_env(config.protocol.output)
+    jinja_env = initialize_jinja_env(jinja_dir, config.protocol.output)
 
     inputs = []
     inputs.append(__file__)
@@ -390,15 +370,16 @@
     inputs.append(config.protocol.path)
     if config.imported:
         inputs.append(config.imported.path)
+    templates_dir = os.path.join(module_path, "templates")
     inputs.append(os.path.join(templates_dir, "TypeBuilder_h.template"))
     inputs.append(os.path.join(templates_dir, "TypeBuilder_cpp.template"))
     inputs.append(os.path.join(templates_dir, "Exported_h.template"))
     inputs.append(os.path.join(templates_dir, "Imported_h.template"))
 
-    h_template = jinja_env.get_template("TypeBuilder_h.template")
-    cpp_template = jinja_env.get_template("TypeBuilder_cpp.template")
-    exported_template = jinja_env.get_template("Exported_h.template")
-    imported_template = jinja_env.get_template("Imported_h.template")
+    h_template = jinja_env.get_template("templates/TypeBuilder_h.template")
+    cpp_template = jinja_env.get_template("templates/TypeBuilder_cpp.template")
+    exported_template = jinja_env.get_template("templates/Exported_h.template")
+    imported_template = jinja_env.get_template("templates/Imported_h.template")
 
     outputs = dict()
 
@@ -426,6 +407,7 @@
             "config": config
         }
 
+        lib_templates_dir = os.path.join(module_path, "lib")
         # Note these should be sorted in the right order.
         # TODO(dgozman): sort them programmatically based on commented includes.
         lib_h_templates = [
@@ -459,8 +441,8 @@
         def generate_lib_file(file_name, template_files):
             parts = []
             for template_file in template_files:
-                inputs.append(os.path.join(templates_dir, template_file))
-                template = jinja_env.get_template(template_file)
+                inputs.append(os.path.join(lib_templates_dir, template_file))
+                template = jinja_env.get_template("lib/" + template_file)
                 parts.append(template.render(template_context))
             outputs[file_name] = "\n\n".join(parts)
 
diff --git a/ConcatenateProtocols.py b/ConcatenateProtocols.py
new file mode 100755
index 0000000..a7cbc99
--- /dev/null
+++ b/ConcatenateProtocols.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os.path
+import sys
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+
+def main(argv):
+    if len(argv) < 1:
+        sys.stderr.write("Usage: %s <protocol-1> [<protocol-2> [, <protocol-3>...]] <output-file>\n" % sys.argv[0])
+        return 1
+
+    domains = []
+    version = None
+    for protocol in argv[:-1]:
+        file_name = os.path.normpath(protocol)
+        if not os.path.isfile(file_name):
+            sys.stderr.write("Cannot find %s\n" % file_name)
+            return 1
+        input_file = open(file_name, "r")
+        json_string = input_file.read()
+        parsed_json = json.loads(json_string)
+        domains += parsed_json["domains"]
+        version = parsed_json["version"]
+
+    output_file = open(argv[-1], "w")
+    json.dump({"version": version, "domains": domains}, output_file, indent=4, sort_keys=False, separators=(',', ': '))
+    output_file.close()
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/generate-inspector-protocol-version b/generate-inspector-protocol-version
deleted file mode 100755
index 8c13092..0000000
--- a/generate-inspector-protocol-version
+++ /dev/null
@@ -1,483 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2011 Google Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-# Inspector protocol validator.
-#
-# Tests that subsequent protocol changes are not breaking backwards compatibility.
-# Following violations are reported:
-#
-#   - Domain has been removed
-#   - Command has been removed
-#   - Required command parameter was added or changed from optional
-#   - Required response parameter was removed or changed to optional
-#   - Event has been removed
-#   - Required event parameter was removed or changed to optional
-#   - Parameter type has changed.
-#
-# For the parameters with composite types the above checks are also applied
-# recursively to every property of the type.
-#
-# Adding --show_changes to the command line prints out a list of valid public API changes.
-
-import collections
-import copy
-import os.path
-import optparse
-import re
-import sys
-
-try:
-    import json
-except ImportError:
-    import simplejson as json
-
-def list_to_map(items, key):
-    result = {}
-    for item in items:
-        if not "experimental" in item and not "hidden" in item:
-            result[item[key]] = item
-    return result
-
-
-def named_list_to_map(container, name, key):
-    if name in container:
-        return list_to_map(container[name], key)
-    return {}
-
-
-def removed(reverse):
-    if reverse:
-        return "added"
-    return "removed"
-
-
-def required(reverse):
-    if reverse:
-        return "optional"
-    return "required"
-
-
-def compare_schemas(d_1, d_2, reverse):
-    errors = []
-    domains_1 = copy.deepcopy(d_1)
-    domains_2 = copy.deepcopy(d_2)
-    types_1 = normalize_types_in_schema(domains_1)
-    types_2 = normalize_types_in_schema(domains_2)
-
-    domains_by_name_1 = list_to_map(domains_1, "domain")
-    domains_by_name_2 = list_to_map(domains_2, "domain")
-
-    for name in domains_by_name_1:
-        domain_1 = domains_by_name_1[name]
-        if not name in domains_by_name_2:
-            errors.append("%s: domain has been %s" % (name, removed(reverse)))
-            continue
-        compare_domains(domain_1, domains_by_name_2[name], types_1, types_2, errors, reverse)
-    return errors
-
-
-def compare_domains(domain_1, domain_2, types_map_1, types_map_2, errors, reverse):
-    domain_name = domain_1["domain"]
-    commands_1 = named_list_to_map(domain_1, "commands", "name")
-    commands_2 = named_list_to_map(domain_2, "commands", "name")
-    for name in commands_1:
-        command_1 = commands_1[name]
-        if not name in commands_2:
-            errors.append("%s.%s: command has been %s" % (domain_1["domain"], name, removed(reverse)))
-            continue
-        compare_commands(domain_name, command_1, commands_2[name], types_map_1, types_map_2, errors, reverse)
-
-    events_1 = named_list_to_map(domain_1, "events", "name")
-    events_2 = named_list_to_map(domain_2, "events", "name")
-    for name in events_1:
-        event_1 = events_1[name]
-        if not name in events_2:
-            errors.append("%s.%s: event has been %s" % (domain_1["domain"], name, removed(reverse)))
-            continue
-        compare_events(domain_name, event_1, events_2[name], types_map_1, types_map_2, errors, reverse)
-
-
-def compare_commands(domain_name, command_1, command_2, types_map_1, types_map_2, errors, reverse):
-    context = domain_name + "." + command_1["name"]
-
-    params_1 = named_list_to_map(command_1, "parameters", "name")
-    params_2 = named_list_to_map(command_2, "parameters", "name")
-    # Note the reversed order: we allow removing but forbid adding parameters.
-    compare_params_list(context, "parameter", params_2, params_1, types_map_2, types_map_1, 0, errors, not reverse)
-
-    returns_1 = named_list_to_map(command_1, "returns", "name")
-    returns_2 = named_list_to_map(command_2, "returns", "name")
-    compare_params_list(context, "response parameter", returns_1, returns_2, types_map_1, types_map_2, 0, errors, reverse)
-
-
-def compare_events(domain_name, event_1, event_2, types_map_1, types_map_2, errors, reverse):
-    context = domain_name + "." + event_1["name"]
-    params_1 = named_list_to_map(event_1, "parameters", "name")
-    params_2 = named_list_to_map(event_2, "parameters", "name")
-    compare_params_list(context, "parameter", params_1, params_2, types_map_1, types_map_2, 0, errors, reverse)
-
-
-def compare_params_list(context, kind, params_1, params_2, types_map_1, types_map_2, depth, errors, reverse):
-    for name in params_1:
-        param_1 = params_1[name]
-        if not name in params_2:
-            if not "optional" in param_1:
-                errors.append("%s.%s: required %s has been %s" % (context, name, kind, removed(reverse)))
-            continue
-
-        param_2 = params_2[name]
-        if param_2 and "optional" in param_2 and not "optional" in param_1:
-            errors.append("%s.%s: %s %s is now %s" % (context, name, required(reverse), kind, required(not reverse)))
-            continue
-        type_1 = extract_type(param_1, types_map_1, errors)
-        type_2 = extract_type(param_2, types_map_2, errors)
-        compare_types(context + "." + name, kind, type_1, type_2, types_map_1, types_map_2, depth, errors, reverse)
-
-
-def compare_types(context, kind, type_1, type_2, types_map_1, types_map_2, depth, errors, reverse):
-    if depth > 10:
-        return
-
-    base_type_1 = type_1["type"]
-    base_type_2 = type_2["type"]
-
-    if base_type_1 != base_type_2:
-        errors.append("%s: %s base type mismatch, '%s' vs '%s'" % (context, kind, base_type_1, base_type_2))
-    elif base_type_1 == "object":
-        params_1 = named_list_to_map(type_1, "properties", "name")
-        params_2 = named_list_to_map(type_2, "properties", "name")
-        # If both parameters have the same named type use it in the context.
-        if "id" in type_1 and "id" in type_2 and type_1["id"] == type_2["id"]:
-            type_name = type_1["id"]
-        else:
-            type_name = "<object>"
-        context += " %s->%s" % (kind, type_name)
-        compare_params_list(context, "property", params_1, params_2, types_map_1, types_map_2, depth + 1, errors, reverse)
-    elif base_type_1 == "array":
-        item_type_1 = extract_type(type_1["items"], types_map_1, errors)
-        item_type_2 = extract_type(type_2["items"], types_map_2, errors)
-        compare_types(context, kind, item_type_1, item_type_2, types_map_1, types_map_2, depth + 1, errors, reverse)
-
-
-def extract_type(typed_object, types_map, errors):
-    if "type" in typed_object:
-        result = { "id": "<transient>", "type": typed_object["type"] }
-        if typed_object["type"] == "object":
-            result["properties"] = []
-        elif typed_object["type"] == "array":
-            result["items"] = typed_object["items"]
-        return result
-    elif "$ref" in typed_object:
-        ref = typed_object["$ref"]
-        if not ref in types_map:
-            errors.append("Can not resolve type: %s" % ref)
-            types_map[ref] = { "id": "<transient>", "type": "object" }
-        return types_map[ref]
-
-
-def normalize_types_in_schema(domains):
-    types = {}
-    for domain in domains:
-        domain_name = domain["domain"]
-        normalize_types(domain, domain_name, types)
-    return types
-
-
-def normalize_types(obj, domain_name, types):
-    if isinstance(obj, list):
-        for item in obj:
-            normalize_types(item, domain_name, types)
-    elif isinstance(obj, dict):
-        for key, value in obj.items():
-            if key == "$ref" and value.find(".") == -1:
-                obj[key] = "%s.%s" % (domain_name, value)
-            elif key == "id":
-                obj[key] = "%s.%s" % (domain_name, value)
-                types[obj[key]] = obj
-            else:
-                normalize_types(value, domain_name, types)
-
-
-def load_schema(file, domains):
-    if not os.path.isfile(file):
-        return
-    input_file = open(file, "r")
-    json_string = input_file.read()
-    parsed_json = json.loads(json_string)
-    domains += parsed_json["domains"]
-    return parsed_json["version"]
-
-
-def self_test():
-    def create_test_schema_1():
-        return [
-        {
-            "domain": "Network",
-            "types": [
-                {
-                    "id": "LoaderId",
-                    "type": "string"
-                },
-                {
-                    "id": "Headers",
-                    "type": "object"
-                },
-                {
-                    "id": "Request",
-                    "type": "object",
-                    "properties": [
-                        { "name": "url", "type": "string" },
-                        { "name": "method", "type": "string" },
-                        { "name": "headers", "$ref": "Headers" },
-                        { "name": "becameOptionalField", "type": "string" },
-                        { "name": "removedField", "type": "string" },
-                    ]
-                }
-            ],
-            "commands": [
-                {
-                    "name": "removedCommand",
-                },
-                {
-                    "name": "setExtraHTTPHeaders",
-                    "parameters": [
-                        { "name": "headers", "$ref": "Headers" },
-                        { "name": "mismatched", "type": "string" },
-                        { "name": "becameOptional", "$ref": "Headers" },
-                        { "name": "removedRequired", "$ref": "Headers" },
-                        { "name": "becameRequired", "$ref": "Headers", "optional": True },
-                        { "name": "removedOptional", "$ref": "Headers", "optional": True },
-                    ],
-                    "returns": [
-                        { "name": "mimeType", "type": "string" },
-                        { "name": "becameOptional", "type": "string" },
-                        { "name": "removedRequired", "type": "string" },
-                        { "name": "becameRequired", "type": "string", "optional": True },
-                        { "name": "removedOptional", "type": "string", "optional": True },
-                    ]
-                }
-            ],
-            "events": [
-                {
-                    "name": "requestWillBeSent",
-                    "parameters": [
-                        { "name": "frameId", "type": "string", "experimental": True },
-                        { "name": "request", "$ref": "Request" },
-                        { "name": "becameOptional", "type": "string" },
-                        { "name": "removedRequired", "type": "string" },
-                        { "name": "becameRequired", "type": "string", "optional": True },
-                        { "name": "removedOptional", "type": "string", "optional": True },
-                        ]
-                },
-                {
-                    "name": "removedEvent",
-                    "parameters": [
-                        { "name": "errorText", "type": "string" },
-                        { "name": "canceled", "type": "boolean", "optional": True }
-                    ]
-                }
-            ]
-        },
-        {
-            "domain":  "removedDomain"
-        }
-    ]
-
-    def create_test_schema_2():
-        return [
-        {
-            "domain": "Network",
-            "types": [
-                {
-                    "id": "LoaderId",
-                    "type": "string"
-                },
-                {
-                    "id": "Request",
-                    "type": "object",
-                    "properties": [
-                        { "name": "url", "type": "string" },
-                        { "name": "method", "type": "string" },
-                        { "name": "headers", "type": "object" },
-                        { "name": "becameOptionalField", "type": "string", "optional": True },
-                    ]
-                }
-            ],
-            "commands": [
-                {
-                    "name": "addedCommand",
-                },
-                {
-                    "name": "setExtraHTTPHeaders",
-                    "parameters": [
-                        { "name": "headers", "type": "object" },
-                        { "name": "mismatched", "type": "object" },
-                        { "name": "becameOptional", "type": "object" , "optional": True },
-                        { "name": "addedRequired", "type": "object" },
-                        { "name": "becameRequired", "type": "object" },
-                        { "name": "addedOptional", "type": "object", "optional": True  },
-                    ],
-                    "returns": [
-                        { "name": "mimeType", "type": "string" },
-                        { "name": "becameOptional", "type": "string", "optional": True },
-                        { "name": "addedRequired", "type": "string"},
-                        { "name": "becameRequired", "type": "string" },
-                        { "name": "addedOptional", "type": "string", "optional": True  },
-                    ]
-                }
-            ],
-            "events": [
-                {
-                    "name": "requestWillBeSent",
-                    "parameters": [
-                        { "name": "request", "$ref": "Request" },
-                        { "name": "becameOptional", "type": "string", "optional": True },
-                        { "name": "addedRequired", "type": "string"},
-                        { "name": "becameRequired", "type": "string" },
-                        { "name": "addedOptional", "type": "string", "optional": True  },
-                    ]
-                },
-                {
-                    "name": "addedEvent"
-                }
-            ]
-        },
-        {
-            "domain": "addedDomain"
-        }
-    ]
-
-    expected_errors = [
-        "removedDomain: domain has been removed",
-        "Network.removedCommand: command has been removed",
-        "Network.removedEvent: event has been removed",
-        "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, 'object' vs 'string'",
-        "Network.setExtraHTTPHeaders.addedRequired: required parameter has been added",
-        "Network.setExtraHTTPHeaders.becameRequired: optional parameter is now required",
-        "Network.setExtraHTTPHeaders.removedRequired: required response parameter has been removed",
-        "Network.setExtraHTTPHeaders.becameOptional: required response parameter is now optional",
-        "Network.requestWillBeSent.removedRequired: required parameter has been removed",
-        "Network.requestWillBeSent.becameOptional: required parameter is now optional",
-        "Network.requestWillBeSent.request parameter->Network.Request.removedField: required property has been removed",
-        "Network.requestWillBeSent.request parameter->Network.Request.becameOptionalField: required property is now optional",
-    ]
-
-    expected_errors_reverse = [
-       "addedDomain: domain has been added",
-       "Network.addedEvent: event has been added",
-       "Network.addedCommand: command has been added",
-       "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, 'string' vs 'object'",
-       "Network.setExtraHTTPHeaders.removedRequired: required parameter has been removed",
-       "Network.setExtraHTTPHeaders.becameOptional: required parameter is now optional",
-       "Network.setExtraHTTPHeaders.addedRequired: required response parameter has been added",
-       "Network.setExtraHTTPHeaders.becameRequired: optional response parameter is now required",
-       "Network.requestWillBeSent.becameRequired: optional parameter is now required",
-       "Network.requestWillBeSent.addedRequired: required parameter has been added",
-    ]
-
-    def is_subset(subset, superset, message):
-        for i in range(len(subset)):
-            if subset[i] not in superset:
-                sys.stderr.write("%s error: %s\n" % (message, subset[i]))
-                return False
-        return True
-
-    def errors_match(expected, actual):
-        return (is_subset(actual, expected, "Unexpected") and
-                is_subset(expected, actual, "Missing"))
-
-    return (errors_match(expected_errors,
-                         compare_schemas(create_test_schema_1(), create_test_schema_2(), False)) and
-            errors_match(expected_errors_reverse,
-                         compare_schemas(create_test_schema_2(), create_test_schema_1(), True)))
-
-
-
-def load_domains_and_baselines(file, domains, baseline_domains):
-    version = load_schema(os.path.normpath(file), domains)
-    suffix = "-%s.%s.json" % (version["major"], version["minor"])
-    baseline_file = file.replace(".json", suffix)
-    load_schema(os.path.normpath(baseline_file), baseline_domains)
-    return version
-
-
-def main():
-    if not self_test():
-        sys.stderr.write("Self-test failed")
-        return 1
-
-    cmdline_parser = optparse.OptionParser()
-    cmdline_parser.add_option("--show_changes")
-    cmdline_parser.add_option("--o")
-    arg_options, arg_values = cmdline_parser.parse_args()
-
-    if len(arg_values) < 1 or not arg_options.o:
-        sys.stderr.write("Usage: %s --o OUTPUT_FILE [--show_changes] PROTOCOL_FOLDER1 ?PROTOCOL_FOLDER2 \n" % sys.argv[0])
-        return 1
-
-    output_path = arg_options.o
-    output_file = open(output_path, "w")
-
-    domains = []
-    baseline_domains = []
-    version = load_domains_and_baselines(arg_values[0], domains, baseline_domains)
-    if len(arg_values) > 1:
-        load_domains_and_baselines(arg_values[1], domains, baseline_domains)
-
-    expected_errors = [
-        "Debugger.globalObjectCleared: event has been removed",
-        "Runtime.executionContextCreated.context parameter->Runtime.ExecutionContextDescription.frameId: required property has been removed",
-        "Debugger.canSetScriptSource: command has been removed",
-        "Console.messageRepeatCountUpdated: event has been removed",
-        "Console.messagesCleared: event has been removed"
-    ]
-
-    errors = compare_schemas(baseline_domains, domains, False)
-    unexpected_errors = []
-    for i in range(len(errors)):
-        if errors[i] not in expected_errors:
-            unexpected_errors.append(errors[i])
-    if len(unexpected_errors) > 0:
-        sys.stderr.write("  Compatibility checks FAILED\n")
-        for error in unexpected_errors:
-            sys.stderr.write( "    %s\n" % error)
-        return 1
-
-    if arg_options.show_changes:
-        changes = compare_schemas(domains, baseline_domains, True)
-        if len(changes) > 0:
-            print "  Public changes since %s:" % version
-            for change in changes:
-                print "    %s" % change
-
-    json.dump({"version": version, "domains": domains}, output_file, indent=4, sort_keys=False, separators=(',', ': '))
-    output_file.close()
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/inspector_protocol.gni b/inspector_protocol.gni
new file mode 100644
index 0000000..324062e
--- /dev/null
+++ b/inspector_protocol.gni
@@ -0,0 +1,32 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+inspector_protocol_sources =
+    get_path_info([
+                    "lib/Allocator_h.template",
+                    "lib/Array_h.template",
+                    "lib/BackendCallback_h.template",
+                    "lib/Collections_h.template",
+                    "lib/DispatcherBase_cpp.template",
+                    "lib/DispatcherBase_h.template",
+                    "lib/ErrorSupport_cpp.template",
+                    "lib/ErrorSupport_h.template",
+                    "lib/Forward_h.template",
+                    "lib/FrontendChannel_h.template",
+                    "lib/Maybe_h.template",
+                    "lib/Object_cpp.template",
+                    "lib/Object_h.template",
+                    "lib/Parser_cpp.template",
+                    "lib/Parser_h.template",
+                    "lib/Protocol_cpp.template",
+                    "lib/ValueConversions_h.template",
+                    "lib/Values_cpp.template",
+                    "lib/Values_h.template",
+                    "templates/Exported_h.template",
+                    "templates/Imported_h.template",
+                    "templates/TypeBuilder_cpp.template",
+                    "templates/TypeBuilder_h.template",
+                    "CodeGenerator.py",
+                  ],
+                  "abspath")
diff --git a/inspector_protocol.gypi b/inspector_protocol.gypi
new file mode 100644
index 0000000..88ee14c
--- /dev/null
+++ b/inspector_protocol.gypi
@@ -0,0 +1,33 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+{
+  'variables': {
+    'inspector_protocol_files': [
+      'lib/Allocator_h.template',
+      'lib/Array_h.template',
+      'lib/BackendCallback_h.template',
+      'lib/Collections_h.template',
+      'lib/DispatcherBase_cpp.template',
+      'lib/DispatcherBase_h.template',
+      'lib/ErrorSupport_cpp.template',
+      'lib/ErrorSupport_h.template',
+      'lib/Forward_h.template',
+      'lib/FrontendChannel_h.template',
+      'lib/Maybe_h.template',
+      'lib/Object_cpp.template',
+      'lib/Object_h.template',
+      'lib/Parser_cpp.template',
+      'lib/Parser_h.template',
+      'lib/Protocol_cpp.template',
+      'lib/ValueConversions_h.template',
+      'lib/Values_cpp.template',
+      'lib/Values_h.template',
+      'templates/Exported_h.template',
+      'templates/Imported_h.template',
+      'templates/TypeBuilder_cpp.template',
+      'templates/TypeBuilder_h.template',
+      'CodeGenerator.py',
+    ]
+  }
+}
diff --git a/Allocator_h.template b/lib/Allocator_h.template
similarity index 100%
rename from Allocator_h.template
rename to lib/Allocator_h.template
diff --git a/Array_h.template b/lib/Array_h.template
similarity index 100%
rename from Array_h.template
rename to lib/Array_h.template
diff --git a/BackendCallback_h.template b/lib/BackendCallback_h.template
similarity index 100%
rename from BackendCallback_h.template
rename to lib/BackendCallback_h.template
diff --git a/Collections_h.template b/lib/Collections_h.template
similarity index 100%
rename from Collections_h.template
rename to lib/Collections_h.template
diff --git a/DispatcherBase_cpp.template b/lib/DispatcherBase_cpp.template
similarity index 100%
rename from DispatcherBase_cpp.template
rename to lib/DispatcherBase_cpp.template
diff --git a/DispatcherBase_h.template b/lib/DispatcherBase_h.template
similarity index 100%
rename from DispatcherBase_h.template
rename to lib/DispatcherBase_h.template
diff --git a/ErrorSupport_cpp.template b/lib/ErrorSupport_cpp.template
similarity index 100%
rename from ErrorSupport_cpp.template
rename to lib/ErrorSupport_cpp.template
diff --git a/ErrorSupport_h.template b/lib/ErrorSupport_h.template
similarity index 100%
rename from ErrorSupport_h.template
rename to lib/ErrorSupport_h.template
diff --git a/Forward_h.template b/lib/Forward_h.template
similarity index 100%
rename from Forward_h.template
rename to lib/Forward_h.template
diff --git a/FrontendChannel_h.template b/lib/FrontendChannel_h.template
similarity index 100%
rename from FrontendChannel_h.template
rename to lib/FrontendChannel_h.template
diff --git a/Maybe_h.template b/lib/Maybe_h.template
similarity index 100%
rename from Maybe_h.template
rename to lib/Maybe_h.template
diff --git a/Object_cpp.template b/lib/Object_cpp.template
similarity index 100%
rename from Object_cpp.template
rename to lib/Object_cpp.template
diff --git a/Object_h.template b/lib/Object_h.template
similarity index 100%
rename from Object_h.template
rename to lib/Object_h.template
diff --git a/Parser_cpp.template b/lib/Parser_cpp.template
similarity index 99%
rename from Parser_cpp.template
rename to lib/Parser_cpp.template
index b58732a..94fb1a2 100644
--- a/Parser_cpp.template
+++ b/lib/Parser_cpp.template
@@ -325,7 +325,7 @@
         return c - 'A' + 10;
     if ('a' <= c && c <= 'f')
         return c - 'a' + 10;
-    NOTREACHED();
+    DCHECK(false);
     return 0;
 }
 
diff --git a/Parser_h.template b/lib/Parser_h.template
similarity index 100%
rename from Parser_h.template
rename to lib/Parser_h.template
diff --git a/Protocol_cpp.template b/lib/Protocol_cpp.template
similarity index 100%
rename from Protocol_cpp.template
rename to lib/Protocol_cpp.template
diff --git a/ValueConversions_h.template b/lib/ValueConversions_h.template
similarity index 100%
rename from ValueConversions_h.template
rename to lib/ValueConversions_h.template
diff --git a/Values_cpp.template b/lib/Values_cpp.template
similarity index 99%
rename from Values_cpp.template
rename to lib/Values_cpp.template
index 9c5d8cf..1b5cdfe 100644
--- a/Values_cpp.template
+++ b/lib/Values_cpp.template
@@ -167,7 +167,7 @@
     case TypeInteger: return FundamentalValue::create(m_integerValue);
     case TypeBoolean: return FundamentalValue::create(m_boolValue);
     default:
-        NOTREACHED();
+        DCHECK(false);
     }
     return nullptr;
 }
diff --git a/Values_h.template b/lib/Values_h.template
similarity index 100%
rename from Values_h.template
rename to lib/Values_h.template
diff --git a/sample_expected_errors.json b/sample_expected_errors.json
new file mode 100644
index 0000000..85fa264
--- /dev/null
+++ b/sample_expected_errors.json
@@ -0,0 +1,7 @@
+{
+    "errors": [
+        "Domain.event: event has been removed",
+        "Domain.command: command has been removed",
+        "Domain.command.param parameter->Domain.TypeName.property: required property has been removed"
+    ]
+}
\ No newline at end of file
diff --git a/Exported_h.template b/templates/Exported_h.template
similarity index 100%
rename from Exported_h.template
rename to templates/Exported_h.template
diff --git a/Imported_h.template b/templates/Imported_h.template
similarity index 100%
rename from Imported_h.template
rename to templates/Imported_h.template
diff --git a/TypeBuilder_cpp.template b/templates/TypeBuilder_cpp.template
similarity index 100%
rename from TypeBuilder_cpp.template
rename to templates/TypeBuilder_cpp.template
diff --git a/TypeBuilder_h.template b/templates/TypeBuilder_h.template
similarity index 100%
rename from TypeBuilder_h.template
rename to templates/TypeBuilder_h.template