Przeglądaj źródła

Meta+BindingsGenerator: Only invoke BindingsGenerator once per IDL file

This reduces the number of tasks to schedule, and the complexity of the
build system integrations for the BindingsGenerator. As a bonus, we move
the "only write if changed" feature into the generator to reduce the
build system load on generated files for this generator.
Andrew Kaster 2 lat temu
rodzic
commit
3dd3120a8a

+ 27 - 59
Meta/CMake/libweb_generators.cmake

@@ -97,16 +97,11 @@ function (generate_js_bindings target)
         cmake_parse_arguments(PARSE_ARGV 1 LIBWEB_BINDINGS "NAMESPACE;ITERABLE;GLOBAL" "" "")
         cmake_parse_arguments(PARSE_ARGV 1 LIBWEB_BINDINGS "NAMESPACE;ITERABLE;GLOBAL" "" "")
         get_filename_component(basename "${class}" NAME)
         get_filename_component(basename "${class}" NAME)
 
 
-        # FIXME: Instead of requiring a manual declaration of namespace bindings, we should ask BindingsGenerator if it's a namespace
         if (LIBWEB_BINDINGS_NAMESPACE)
         if (LIBWEB_BINDINGS_NAMESPACE)
             set(BINDINGS_SOURCES
             set(BINDINGS_SOURCES
                 "Bindings/${basename}Namespace.h"
                 "Bindings/${basename}Namespace.h"
                 "Bindings/${basename}Namespace.cpp"
                 "Bindings/${basename}Namespace.cpp"
             )
             )
-            set(BINDINGS_TYPES
-                namespace-header
-                namespace-implementation
-            )
         else()
         else()
             set(BINDINGS_SOURCES
             set(BINDINGS_SOURCES
                 "Bindings/${basename}Constructor.h"
                 "Bindings/${basename}Constructor.h"
@@ -114,77 +109,50 @@ function (generate_js_bindings target)
                 "Bindings/${basename}Prototype.h"
                 "Bindings/${basename}Prototype.h"
                 "Bindings/${basename}Prototype.cpp"
                 "Bindings/${basename}Prototype.cpp"
             )
             )
-            set(BINDINGS_TYPES
-                constructor-header
-                constructor-implementation
-                prototype-header
-                prototype-implementation
-            )
         endif()
         endif()
 
 
-        # FIXME: Instead of requiring a manual declaration of iterable bindings, we should ask BindingsGenerator if it's iterable
         if(LIBWEB_BINDINGS_ITERABLE)
         if(LIBWEB_BINDINGS_ITERABLE)
             list(APPEND BINDINGS_SOURCES
             list(APPEND BINDINGS_SOURCES
                 "Bindings/${basename}IteratorPrototype.h"
                 "Bindings/${basename}IteratorPrototype.h"
                 "Bindings/${basename}IteratorPrototype.cpp"
                 "Bindings/${basename}IteratorPrototype.cpp"
             )
             )
-            list(APPEND BINDINGS_TYPES
-                iterator-prototype-header
-                iterator-prototype-implementation
-            )
         endif()
         endif()
 
 
-        # FIXME: Instead of requiring a manual declaration of global object bindings, we should ask BindingsGenerator if it's global
         if(LIBWEB_BINDINGS_GLOBAL)
         if(LIBWEB_BINDINGS_GLOBAL)
             list(APPEND BINDINGS_SOURCES
             list(APPEND BINDINGS_SOURCES
                 "Bindings/${basename}GlobalMixin.h"
                 "Bindings/${basename}GlobalMixin.h"
                 "Bindings/${basename}GlobalMixin.cpp"
                 "Bindings/${basename}GlobalMixin.cpp"
             )
             )
-            list(APPEND BINDINGS_TYPES
-                global-mixin-header
-                global-mixin-implementation
-            )
         endif()
         endif()
 
 
+        list(TRANSFORM BINDINGS_SOURCES PREPEND "${CMAKE_CURRENT_BINARY_DIR}/")
         target_sources(${target} PRIVATE ${BINDINGS_SOURCES})
         target_sources(${target} PRIVATE ${BINDINGS_SOURCES})
-        # FIXME: cmake_minimum_required(3.17) for ZIP_LISTS
-        list(LENGTH BINDINGS_SOURCES num_bindings)
-        math(EXPR bindings_end "${num_bindings} - 1")
-        foreach(iter RANGE "${bindings_end}")
-            list(GET BINDINGS_SOURCES ${iter} bindings_src)
-            list(GET BINDINGS_TYPES ${iter} bindings_type)
-            get_property(include_paths DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
-            list(TRANSFORM include_paths PREPEND -i)
-
-            # Ninja expects the target name in depfiles to be relative to CMAKE_BINARY_DIR, but ${bindings_src} is
-            # relative to CMAKE_CURRENT_BINARY_DIR. CMake >= 3.20 can do the rewriting transparently (CMP0116).
-            if(CMAKE_GENERATOR MATCHES "^Ninja" AND NOT POLICY CMP0116)
-                # FIXME: Drop this branch for cmake_minimum_required(3.20)
-                get_filename_component(full_path ${bindings_src} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_BINARY_DIR})
-                file(RELATIVE_PATH depfile_target ${CMAKE_BINARY_DIR} ${full_path})
-            else()
-                set(depfile_target ${bindings_src})
-            endif()
-
-            add_custom_command(
-                OUTPUT "${bindings_src}"
-                COMMAND "$<TARGET_FILE:Lagom::BindingsGenerator>" "--${bindings_type}" -o "${bindings_src}.tmp" --depfile "${bindings_src}.d"
-                        --depfile-target "${depfile_target}" ${include_paths} "${LIBWEB_INPUT_FOLDER}/${class}.idl" "${LIBWEB_INPUT_FOLDER}"
-                COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${bindings_src}.tmp" "${bindings_src}"
-                COMMAND "${CMAKE_COMMAND}" -E remove "${bindings_src}.tmp"
-                VERBATIM
-                DEPENDS Lagom::BindingsGenerator
-                MAIN_DEPENDENCY ${class}.idl
-                DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/${bindings_src}.d
-            )
-        endforeach()
-
-        foreach(generated_file IN LISTS BINDINGS_SOURCES)
-            get_filename_component(generated_name ${generated_file} NAME)
-            add_custom_target(generate_${generated_name} DEPENDS ${generated_file})
-            add_dependencies(all_generated generate_${generated_name})
-            add_dependencies(${target} generate_${generated_name})
-        endforeach()
+
+        get_property(include_paths DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
+        list(TRANSFORM include_paths PREPEND -i)
+
+        # Ninja expects the target name in depfiles to be relative to CMAKE_BINARY_DIR, but ${bindings_src} is
+        # relative to CMAKE_CURRENT_BINARY_DIR. CMake >= 3.20 can do the rewriting transparently (CMP0116).
+        set(depfile_prefix_arg "")
+        if(CMAKE_GENERATOR MATCHES "^Ninja" AND NOT POLICY CMP0116)
+            file(RELATIVE_PATH depfile_target ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR})
+            set(depfile_prefix_arg "--depfile-prefix ${depfile_target}" )
+        endif()
+
+        add_custom_command(
+            OUTPUT ${BINDINGS_SOURCES}
+            COMMAND "$<TARGET_FILE:Lagom::BindingsGenerator>" -o "Bindings" --depfile "Bindings/${basename}.d"
+                    ${depfile_prefix_arg} "${LIBWEB_INPUT_FOLDER}/${class}.idl" "${LIBWEB_INPUT_FOLDER}"
+            VERBATIM
+            COMMENT "Generating Bindings for ${class}"
+            DEPENDS Lagom::BindingsGenerator
+            MAIN_DEPENDENCY ${class}.idl
+            DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/Bindings/${basename}.d
+        )
+
+        add_custom_target(generate_${basename} DEPENDS ${BINDINGS_SOURCES})
+        add_dependencies(all_generated generate_${basename})
+        add_dependencies(${target} generate_${basename})
 
 
         list(APPEND LIBWEB_ALL_IDL_FILES "${LIBWEB_INPUT_FOLDER}/${class}.idl")
         list(APPEND LIBWEB_ALL_IDL_FILES "${LIBWEB_INPUT_FOLDER}/${class}.idl")
         set(LIBWEB_ALL_IDL_FILES ${LIBWEB_ALL_IDL_FILES} PARENT_SCOPE)
         set(LIBWEB_ALL_IDL_FILES ${LIBWEB_ALL_IDL_FILES} PARENT_SCOPE)

+ 80 - 56
Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp

@@ -3,6 +3,7 @@
  * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
  * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
+ * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
@@ -37,27 +38,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     StringView import_base_path;
     StringView import_base_path;
     StringView output_path = "-"sv;
     StringView output_path = "-"sv;
     StringView depfile_path;
     StringView depfile_path;
-    StringView depfile_target;
-    bool namespace_header_mode = false;
-    bool namespace_implementation_mode = false;
-    bool constructor_header_mode = false;
-    bool constructor_implementation_mode = false;
-    bool prototype_header_mode = false;
-    bool prototype_implementation_mode = false;
-    bool iterator_prototype_header_mode = false;
-    bool iterator_prototype_implementation_mode = false;
-    bool global_mixin_header_mode = false;
-    bool global_mixin_implementation_mode = false;
-    args_parser.add_option(namespace_header_mode, "Generate the namespace .h file", "namespace-header", 'N');
-    args_parser.add_option(namespace_implementation_mode, "Generate the namespace .cpp file", "namespace-implementation", 'A');
-    args_parser.add_option(constructor_header_mode, "Generate the constructor .h file", "constructor-header", 'C');
-    args_parser.add_option(constructor_implementation_mode, "Generate the constructor .cpp file", "constructor-implementation", 'O');
-    args_parser.add_option(prototype_header_mode, "Generate the prototype .h file", "prototype-header", 'P');
-    args_parser.add_option(prototype_implementation_mode, "Generate the prototype .cpp file", "prototype-implementation", 'R');
-    args_parser.add_option(iterator_prototype_header_mode, "Generate the iterator prototype .h file", "iterator-prototype-header", 0);
-    args_parser.add_option(iterator_prototype_implementation_mode, "Generate the iterator prototype .cpp file", "iterator-prototype-implementation", 0);
-    args_parser.add_option(global_mixin_header_mode, "Generate the global object mixin .h file", "global-mixin-header", 0);
-    args_parser.add_option(global_mixin_implementation_mode, "Generate the global object mixin .cpp file", "global-mixin-implementation", 0);
+    StringView depfile_prefix;
+
     args_parser.add_option(Core::ArgsParser::Option {
     args_parser.add_option(Core::ArgsParser::Option {
         .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
         .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
         .help_string = "Add a header search path passed to the compiler",
         .help_string = "Add a header search path passed to the compiler",
@@ -69,28 +51,28 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
             return true;
             return true;
         },
         },
     });
     });
-    args_parser.add_option(output_path, "Path to output generated file into", "output-path", 'o', "output-path");
+    args_parser.add_option(output_path, "Path to output generated files into", "output-path", 'o', "output-path");
     args_parser.add_option(depfile_path, "Path to write dependency file to", "depfile", 'd', "depfile-path");
     args_parser.add_option(depfile_path, "Path to write dependency file to", "depfile", 'd', "depfile-path");
-    args_parser.add_option(depfile_target, "Name of target in the depfile (default: output path)", "depfile-target", 't', "target");
+    args_parser.add_option(depfile_prefix, "Prefix to prepend to relative paths in dependency file", "depfile-prefix", 'p', "depfile-prefix");
     args_parser.add_positional_argument(path, "IDL file", "idl-file");
     args_parser.add_positional_argument(path, "IDL file", "idl-file");
     args_parser.add_positional_argument(import_base_path, "Import base path", "import-base-path", Core::ArgsParser::Required::No);
     args_parser.add_positional_argument(import_base_path, "Import base path", "import-base-path", Core::ArgsParser::Required::No);
     args_parser.parse(arguments);
     args_parser.parse(arguments);
 
 
-    auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read));
+    auto idl_file = TRY(Core::File::open(path, Core::File::OpenMode::Read));
 
 
     LexicalPath lexical_path(path);
     LexicalPath lexical_path(path);
     auto& namespace_ = lexical_path.parts_view().at(lexical_path.parts_view().size() - 2);
     auto& namespace_ = lexical_path.parts_view().at(lexical_path.parts_view().size() - 2);
 
 
-    auto data = TRY(file->read_until_eof());
+    auto data = TRY(idl_file->read_until_eof());
 
 
     if (import_base_path.is_null())
     if (import_base_path.is_null())
         import_base_path = lexical_path.dirname();
         import_base_path = lexical_path.dirname();
 
 
-    auto output_file = TRY(Core::File::open_file_or_standard_stream(output_path, Core::File::OpenMode::Write));
-
     IDL::Parser parser(path, data, import_base_path);
     IDL::Parser parser(path, data, import_base_path);
     auto& interface = parser.parse();
     auto& interface = parser.parse();
 
 
+    // If the interface name is the same as its namespace, qualify the name in the generated code.
+    // e.g. Selection::Selection
     if (IDL::libweb_interface_namespaces.span().contains_slow(namespace_)) {
     if (IDL::libweb_interface_namespaces.span().contains_slow(namespace_)) {
         StringBuilder builder;
         StringBuilder builder;
         builder.append(namespace_);
         builder.append(namespace_);
@@ -143,43 +125,85 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
 
 
     StringBuilder output_builder;
     StringBuilder output_builder;
 
 
-    if (namespace_header_mode)
-        IDL::generate_namespace_header(interface, output_builder);
-
-    if (namespace_implementation_mode)
-        IDL::generate_namespace_implementation(interface, output_builder);
-
-    if (constructor_header_mode)
-        IDL::generate_constructor_header(interface, output_builder);
-
-    if (constructor_implementation_mode)
-        IDL::generate_constructor_implementation(interface, output_builder);
-
-    if (prototype_header_mode)
-        IDL::generate_prototype_header(interface, output_builder);
-
-    if (prototype_implementation_mode)
-        IDL::generate_prototype_implementation(interface, output_builder);
-
-    if (iterator_prototype_header_mode)
-        IDL::generate_iterator_prototype_header(interface, output_builder);
+    auto write_if_changed = [&](auto generator_function, StringView file_path) -> ErrorOr<void> {
+        (*generator_function)(interface, output_builder);
+
+        auto output_file = TRY(Core::File::open(file_path, Core::File::OpenMode::ReadWrite));
+
+        // Only write to disk if contents have changed
+        auto previous_contents = TRY(output_file->read_until_eof());
+        TRY(output_file->seek(0, SeekMode::SetPosition));
+        if (previous_contents != output_builder.string_view())
+            TRY(output_file->write_until_depleted(output_builder.string_view().bytes()));
+        // FIXME: Can we add clear_with_capacity to StringBuilder instead of throwing away the allocated buffer?
+        output_builder.clear();
+        return {};
+    };
+
+    String namespace_header;
+    String namespace_implementation;
+    String constructor_header;
+    String constructor_implementation;
+    String prototype_header;
+    String prototype_implementation;
+    String iterator_prototype_header;
+    String iterator_prototype_implementation;
+    String global_mixin_header;
+    String global_mixin_implementation;
+
+    auto path_prefix = LexicalPath::join(output_path, lexical_path.basename(LexicalPath::StripExtension::Yes));
+
+    if (interface.is_namespace) {
+        namespace_header = TRY(String::formatted("{}Namespace.h", path_prefix));
+        namespace_implementation = TRY(String::formatted("{}Namespace.cpp", path_prefix));
+
+        TRY(write_if_changed(&IDL::generate_namespace_header, namespace_header));
+        TRY(write_if_changed(&IDL::generate_namespace_implementation, namespace_implementation));
+    } else {
+        constructor_header = TRY(String::formatted("{}Constructor.h", path_prefix));
+        constructor_implementation = TRY(String::formatted("{}Constructor.cpp", path_prefix));
+        prototype_header = TRY(String::formatted("{}Prototype.h", path_prefix));
+        prototype_implementation = TRY(String::formatted("{}Prototype.cpp", path_prefix));
+
+        TRY(write_if_changed(&IDL::generate_constructor_header, constructor_header));
+        TRY(write_if_changed(&IDL::generate_constructor_implementation, constructor_implementation));
+        TRY(write_if_changed(&IDL::generate_prototype_header, prototype_header));
+        TRY(write_if_changed(&IDL::generate_prototype_implementation, prototype_implementation));
+    }
 
 
-    if (iterator_prototype_implementation_mode)
-        IDL::generate_iterator_prototype_implementation(interface, output_builder);
+    if (interface.pair_iterator_types.has_value()) {
+        iterator_prototype_header = TRY(String::formatted("{}IteratorPrototype.h", path_prefix));
+        iterator_prototype_implementation = TRY(String::formatted("{}IteratorPrototype.cpp", path_prefix));
 
 
-    if (global_mixin_header_mode)
-        IDL::generate_global_mixin_header(interface, output_builder);
+        TRY(write_if_changed(&IDL::generate_iterator_prototype_header, iterator_prototype_header));
+        TRY(write_if_changed(&IDL::generate_iterator_prototype_implementation, iterator_prototype_implementation));
+    }
 
 
-    if (global_mixin_implementation_mode)
-        IDL::generate_global_mixin_implementation(interface, output_builder);
+    if (interface.extended_attributes.contains("Global")) {
+        global_mixin_header = TRY(String::formatted("{}GlobalMixin.h", path_prefix));
+        global_mixin_implementation = TRY(String::formatted("{}GlobalMixin.cpp", path_prefix));
 
 
-    TRY(output_file->write_until_depleted(output_builder.string_view().bytes()));
+        TRY(write_if_changed(&IDL::generate_global_mixin_header, global_mixin_header));
+        TRY(write_if_changed(&IDL::generate_global_mixin_implementation, global_mixin_implementation));
+    }
 
 
-    if (!depfile_path.is_null()) {
+    if (!depfile_path.is_empty()) {
         auto depfile = TRY(Core::File::open_file_or_standard_stream(depfile_path, Core::File::OpenMode::Write));
         auto depfile = TRY(Core::File::open_file_or_standard_stream(depfile_path, Core::File::OpenMode::Write));
 
 
         StringBuilder depfile_builder;
         StringBuilder depfile_builder;
-        depfile_builder.append(depfile_target.is_null() ? output_path : depfile_target);
+        bool first_file = true;
+        for (StringView s : { constructor_header, constructor_implementation, prototype_header, prototype_implementation, namespace_header, namespace_implementation, iterator_prototype_header, iterator_prototype_implementation, global_mixin_header, global_mixin_implementation }) {
+            if (!s.is_empty()) {
+                if (!first_file) {
+                    depfile_builder.append(' ');
+                }
+                if (!depfile_prefix.is_empty())
+                    depfile_builder.append(LexicalPath::join(depfile_prefix, s).string());
+                else
+                    depfile_builder.append(s);
+                first_file = false;
+            }
+        }
         depfile_builder.append(':');
         depfile_builder.append(':');
         for (auto const& path : parser.imported_files()) {
         for (auto const& path : parser.imported_files()) {
             depfile_builder.append(" \\\n "sv);
             depfile_builder.append(" \\\n "sv);

+ 71 - 301
Meta/gn/secondary/Userland/Libraries/LibWeb/generate_idl_bindings.gni

@@ -16,6 +16,9 @@
 #   type (required) string
 #   type (required) string
 #       "global", "iterable", "namespace", or "standard"
 #       "global", "iterable", "namespace", or "standard"
 #
 #
+#   include_dirs (optional) [string]
+#     List of directories to look for imported IDL files in
+#
 # Example use:
 # Example use:
 #
 #
 #  standard_idl_files = [
 #  standard_idl_files = [
@@ -38,320 +41,87 @@
 
 
 import("//Meta/gn/build/compiled_action.gni")
 import("//Meta/gn/build/compiled_action.gni")
 
 
-# FIXME: rewrite these in terms of action_foreach
-template("_invoke_bindings_generator") {
-  # FIXME: Can we update the bindings generator to output the .h and .cpp at the same time?
-
-  assert(defined(invoker.type), "$target_name must have 'type' defined")
-
-  # FIXME: This is pretty gross. Can we make the source file name match the command line argument to the generator more closely?
-  #        GN can't (and probably shouldn't) siwzzle our strings for us in this way automagically
-  assert(defined(invoker.type_filename_fragment),
-         "$target_name must have 'type_filename_fragment' defined")
-  assert(defined(invoker.name), "$target_name must have 'name' defined")
-  assert(defined(invoker.path), "$target_name must have 'path' defined")
-
-  forward_variables_from(invoker,
-                         [
-                           "configs",
-                           "include_dirs",
-                           "public_configs",
-                           "testonly",
-                           "visibility",
-                         ])
-
-  if (!defined(include_dirs)) {
-    include_dirs = [ "//Userland/Libraries" ]
-  }
-  rel_include_dirs = []
-  foreach(d, include_dirs) {
-    rel_include_dirs += [
-      "-i",
-      rebase_path(d, root_build_dir),
-    ]
-  }
-
-  name_and_type = string_replace(invoker.name + "_" + invoker.type, "-", "_")
-  type = invoker.type
-  gen_dir = get_label_info("//Userland/Libraries/LibWeb", "target_gen_dir")
-
-  out_name = get_path_info(invoker.path, "name")
-
-  compiled_action(name_and_type + "_impl") {
-    tool = "//Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator"
-    inputs = [ invoker.path + ".idl" ]
-    outputs = [ gen_dir + "/Bindings/" + out_name +
-                invoker.type_filename_fragment + ".cpp" ]
-    depfile = outputs[0] + ".d"
-    args = [
-             "--$type-implementation",
-             "-o",
-             rebase_path(outputs[0], root_build_dir),
-             "--depfile",
-             rebase_path(depfile, root_build_dir),
-             "--depfile-target",
-             rebase_path(outputs[0], root_build_dir),
-           ] + rel_include_dirs +
-           [
-             rebase_path(inputs[0], root_build_dir),
-
-             # FIXME: Get caller path from invoker?
-             rebase_path("//Userland/Libraries/LibWeb", root_build_dir),
-           ]
-  }
-
-  compiled_action(name_and_type + "_header") {
-    tool = "//Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator"
-    inputs = [ invoker.path + ".idl" ]
-    outputs = [ gen_dir + "/Bindings/" + out_name +
-                invoker.type_filename_fragment + ".h" ]
-    depfile = outputs[0] + ".d"
-    args = [
-             "--$type-header",
-             "-o",
-             rebase_path(outputs[0], root_build_dir),
-             "--depfile",
-             rebase_path(depfile, root_build_dir),
-             "--depfile-target",
-             rebase_path(outputs[0], root_build_dir),
-           ] + rel_include_dirs +
-           [
-             rebase_path(inputs[0], root_build_dir),
-
-             # FIXME: Get caller path from invoker?
-             rebase_path("//Userland/Libraries/LibWeb", root_build_dir),
-           ]
-  }
-
-  source_set("generate_" + name_and_type) {
-    deps = [
-      ":" + name_and_type + "_impl",
-      ":" + name_and_type + "_header",
-    ]
-  }
-
-  source_set(name_and_type + "_sources") {
-    deps = [
-      ":" + name_and_type + "_impl",
-      ":" + name_and_type + "_header",
-    ]
-    sources = get_target_outputs(deps[0]) + get_target_outputs(deps[1])
-    configs += [ "//Userland/Libraries/LibWeb:configs" ]
-    deps += [ "//Userland/Libraries/LibWeb:all_generated" ]
-  }
-}
-
-# FIXME: Deduplicate these templates
-template("_bind_web_namespace") {
-  forward_variables_from(invoker,
-                         [
-                           "configs",
-                           "inputs",
-                           "include_dirs",
-                           "outputs",
-                           "public_configs",
-                           "testonly",
-                           "visibility",
-                         ])
-
-  interface_name = target_name
-
-  _invoke_bindings_generator(interface_name) {
-    type = "namespace"
-    type_filename_fragment = "Namespace"
-    name = interface_name
-    path = invoker.path
-  }
-
-  source_set(interface_name + "_sources") {
-    deps = [ ":" + interface_name + "_namespace_sources" ]
-  }
-
-  source_set("generate_" + interface_name) {
-    deps = [ ":generate_" + interface_name + "_namespace" ]
-  }
-}
-
-# FIXME: Deduplicate these templates
-template("_bind_web_iterable_interface") {
-  forward_variables_from(invoker,
-                         [
-                           "configs",
-                           "inputs",
-                           "include_dirs",
-                           "outputs",
-                           "public_configs",
-                           "testonly",
-                           "visibility",
-                         ])
-
-  interface_name = target_name
-
-  _invoke_bindings_generator(interface_name) {
-    type = "prototype"
-    type_filename_fragment = "Prototype"
-    name = interface_name
-    path = invoker.path
-  }
-
-  _invoke_bindings_generator(interface_name) {
-    type = "constructor"
-    type_filename_fragment = "Constructor"
-    name = interface_name
-    path = invoker.path
-  }
-
-  _invoke_bindings_generator(interface_name) {
-    type = "iterator-prototype"
-    type_filename_fragment = "IteratorPrototype"
-    name = interface_name
-    path = invoker.path
-  }
-
-  source_set(interface_name + "_sources") {
-    deps = [
-      ":" + interface_name + "_prototype_sources",
-      ":" + interface_name + "_constructor_sources",
-      ":" + interface_name + "_iterator_prototype_sources",
-    ]
-  }
-
-  source_set("generate_" + interface_name) {
-    deps = [
-      ":generate_" + interface_name + "_prototype",
-      ":generate_" + interface_name + "_constructor",
-      ":generate_" + interface_name + "_iterator_prototype",
-    ]
-  }
-}
-
-# FIXME: Deduplicate these templates
-template("_bind_web_global_interface") {
-  forward_variables_from(invoker,
-                         [
-                           "configs",
-                           "inputs",
-                           "include_dirs",
-                           "outputs",
-                           "public_configs",
-                           "testonly",
-                           "visibility",
-                         ])
-
-  interface_name = target_name
-
-  _invoke_bindings_generator(interface_name) {
-    type = "prototype"
-    type_filename_fragment = "Prototype"
-    name = interface_name
-    path = invoker.path
-  }
-
-  _invoke_bindings_generator(interface_name) {
-    type = "constructor"
-    type_filename_fragment = "Constructor"
-    name = interface_name
-    path = invoker.path
-  }
-
-  _invoke_bindings_generator(interface_name) {
-    type = "global-mixin"
-    type_filename_fragment = "GlobalMixin"
-    name = interface_name
-    path = invoker.path
-  }
-
-  source_set(interface_name + "_sources") {
-    deps = [
-      ":" + interface_name + "_prototype_sources",
-      ":" + interface_name + "_constructor_sources",
-      ":" + interface_name + "_global_mixin_sources",
-    ]
-  }
-
-  source_set("generate_" + interface_name) {
-    deps = [
-      ":generate_" + interface_name + "_prototype",
-      ":generate_" + interface_name + "_constructor",
-      ":generate_" + interface_name + "_global_mixin",
-    ]
-  }
-}
-
-# FIXME: Deduplicate these templates
-template("_bind_web_interface") {
-  forward_variables_from(invoker,
-                         [
-                           "configs",
-                           "inputs",
-                           "include_dirs",
-                           "outputs",
-                           "public_configs",
-                           "testonly",
-                           "visibility",
-                         ])
-
-  interface_name = target_name
-
-  _invoke_bindings_generator(interface_name) {
-    type = "prototype"
-    type_filename_fragment = "Prototype"
-    name = interface_name
-    path = invoker.path
-  }
-
-  _invoke_bindings_generator(interface_name) {
-    type = "constructor"
-    type_filename_fragment = "Constructor"
-    name = interface_name
-    path = invoker.path
-  }
-
-  source_set(interface_name + "_sources") {
-    deps = [
-      ":" + interface_name + "_prototype_sources",
-      ":" + interface_name + "_constructor_sources",
-    ]
-  }
-
-  source_set("generate_" + interface_name) {
-    deps = [
-      ":generate_" + interface_name + "_prototype",
-      ":generate_" + interface_name + "_constructor",
-    ]
-  }
-}
-
 template("generate_idl_bindings") {
 template("generate_idl_bindings") {
   forward_variables_from(invoker,
   forward_variables_from(invoker,
                          [
                          [
                            "type",
                            "type",
                            "idl_list",
                            "idl_list",
+                           "include_dirs",
                          ])
                          ])
   idl_sources = []
   idl_sources = []
   generate_idl = []
   generate_idl = []
+  gen_dir = get_label_info("//Userland/Libraries/LibWeb", "target_gen_dir") +
+            "/Bindings/"
   foreach(idl, idl_list) {
   foreach(idl, idl_list) {
+    out_name = get_path_info(idl, "name")
     path = get_path_info(rebase_path(idl, "//Userland/Libraries/LibWeb"),
     path = get_path_info(rebase_path(idl, "//Userland/Libraries/LibWeb"),
-                         "dir") + "/" + get_path_info(idl, "name")
+                         "dir") + "/" + out_name
     name = string_replace(path, "/", "_")
     name = string_replace(path, "/", "_")
-    if (type == "standard") {
-      _bind_web_interface(name) {
-        path = path
-      }
-    } else if (type == "iterable") {
-      _bind_web_iterable_interface(name) {
-        path = path
-      }
-    } else if (type == "namespace") {
-      _bind_web_namespace(name) {
-        path = path
-      }
+
+    output_files = []
+    if (type == "namespace") {
+      output_files += [
+        "${gen_dir}${out_name}Namespace.h",
+        "${gen_dir}${out_name}Namespace.cpp",
+      ]
     } else {
     } else {
-      assert(type == "global")
-      _bind_web_global_interface(name) {
-        path = path
-      }
+      output_files += [
+        "${gen_dir}${out_name}Constructor.h",
+        "${gen_dir}${out_name}Constructor.cpp",
+        "${gen_dir}${out_name}Prototype.h",
+        "${gen_dir}${out_name}Prototype.cpp",
+      ]
+    }
+    if (type == "iterable") {
+      output_files += [
+        "${gen_dir}${out_name}IteratorPrototype.h",
+        "${gen_dir}${out_name}IteratorPrototype.cpp",
+      ]
+    }
+    if (type == "global") {
+      output_files += [
+        "${gen_dir}${out_name}GlobalMixin.h",
+        "${gen_dir}${out_name}GlobalMixin.cpp",
+      ]
+    }
+
+    if (!defined(include_dirs)) {
+      include_dirs = [ "//Userland/Libraries" ]
     }
     }
+    rel_include_dirs = []
+    foreach(d, include_dirs) {
+      rel_include_dirs += [
+        "-i",
+        rebase_path(d, root_build_dir),
+      ]
+    }
+
+    compiled_action("generate_" + name) {
+      tool = "//Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator"
+      inputs = [ path + ".idl" ]
+      outputs = output_files
+      depfile = "${gen_dir}${out_name}.d"
+      args = [
+               "-o",
+               rebase_path(gen_dir, root_build_dir),
+               "--depfile",
+               rebase_path(depfile, root_build_dir),
+             ] + rel_include_dirs +
+             [
+               rebase_path(inputs[0], root_build_dir),
+               rebase_path("//Userland/Libraries/LibWeb", root_build_dir),
+             ]
+    }
+
+    source_set(name + "_sources") {
+      deps = [
+        ":generate_" + name,
+        "//Userland/Libraries/LibWeb:all_generated",
+      ]
+      sources = get_target_outputs(deps[0])
+      configs += [ "//Userland/Libraries/LibWeb:configs" ]
+    }
+
     generate_idl += [ ":generate_" + name ]
     generate_idl += [ ":generate_" + name ]
     idl_sources += [ ":" + name + "_sources" ]
     idl_sources += [ ":" + name + "_sources" ]
   }
   }