Files
ladybird/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp
Shannon Booth 83f4e03045 LibWeb/Bindings: Batch BindingsGenerator over all IDL files
Teach BindingsGenerator to parse and generate bindings for the full
LibWeb IDL set in one invocation, and collapse the CMake bindings
rules from one custom command per IDL file to a single batched codegen
step.

This has the downsides of:
 * Any single IDL change now reruns the whole bindings generator
 * Per-IDL parallelism at the custom-command level is lost

However, I still feel that this is a worthy trade off as:
 * Generated files are written with write_if_changed(), so rebuilds
   of generated files should not be significantly impacted.
 * It is not a common task to be modifying IDL files
 * Most importantly, giving the IDL generator full knowledge of _all_
   IDL will allow for some simplifications of the bindings generator as
   it has knowledge of all types.
2026-04-24 20:08:29 +02:00

183 lines
6.9 KiB
C++

/*
* Copyright (c) 2020-2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Luke Wilde <lukew@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
*/
#include "IDLGenerators.h"
#include "Namespaces.h"
#include <AK/Assertions.h>
#include <AK/Debug.h>
#include <AK/HashTable.h>
#include <AK/LexicalPath.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibCore/MappedFile.h>
#include <LibIDL/IDLParser.h>
#include <LibIDL/Types.h>
template<typename GeneratorFunction>
static ErrorOr<void> write_if_changed(GeneratorFunction generator_function, IDL::Interface const& interface, StringView file_path)
{
StringBuilder output_builder;
generator_function(interface, output_builder);
auto current_file_or_error = Core::File::open(file_path, Core::File::OpenMode::Read);
if (current_file_or_error.is_error() && current_file_or_error.error().code() != ENOENT)
return current_file_or_error.release_error();
ByteBuffer current_contents;
if (!current_file_or_error.is_error())
current_contents = TRY(current_file_or_error.value()->read_until_eof());
// Only write to disk if contents have changed
if (current_contents != output_builder.string_view().bytes()) {
auto output_file = TRY(Core::File::open(file_path, Core::File::OpenMode::Write | Core::File::OpenMode::Truncate));
TRY(output_file->write_until_depleted(output_builder.string_view().bytes()));
}
return {};
}
static ErrorOr<void> generate_depfile(StringView depfile_path, ReadonlySpan<ByteString> dependency_paths, ReadonlySpan<ByteString> output_files)
{
auto depfile = TRY(Core::File::open_file_or_standard_stream(depfile_path, Core::File::OpenMode::Write));
StringBuilder depfile_builder;
bool first_output = true;
for (auto const& s : output_files) {
if (!first_output)
depfile_builder.append(' ');
depfile_builder.append(s);
first_output = false;
}
depfile_builder.append(':');
for (auto const& path : dependency_paths) {
depfile_builder.append(" \\\n "sv);
depfile_builder.append(path);
}
depfile_builder.append('\n');
return depfile->write_until_depleted(depfile_builder.string_view().bytes());
}
ErrorOr<int> ladybird_main(Main::Arguments arguments)
{
Core::ArgsParser args_parser;
Vector<ByteString> paths;
Vector<ByteString> base_paths;
StringView output_path = "-"sv;
StringView depfile_path;
args_parser.add_option(Core::ArgsParser::Option {
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
.help_string = "Add a header search path passed to the compiler",
.long_name = "header-include-path",
.short_name = 'i',
.value_name = "path",
.accept_value = [&](StringView s) {
IDL::g_header_search_paths.append(s);
return true;
},
});
args_parser.add_option(Core::ArgsParser::Option {
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
.help_string = "Path to root of IDL file tree(s)",
.long_name = "base-path",
.short_name = 'b',
.value_name = "base-path",
.accept_value = [&](StringView s) {
base_paths.append(s);
return true;
},
});
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_positional_argument(paths, "IDL file(s)", "idl-files");
args_parser.parse(arguments);
if (paths.first().starts_with("@"sv)) {
auto file = TRY(Core::File::open(paths.first().substring_view(1), Core::File::OpenMode::Read));
paths.remove(0);
VERIFY(paths.is_empty());
auto string = TRY(file->read_until_eof());
for (auto const& path : StringView(string).split_view('\n')) {
if (path.is_empty())
continue;
paths.append(path);
}
}
IDL::Context context;
Vector<ByteString> dependency_paths;
HashTable<ByteString> seen_dependency_paths;
Vector<ByteString> output_files;
Vector<LexicalPath> lexical_paths;
Vector<IDL::Interface*> interfaces;
auto append_dependency_path = [&](ByteString const& dependency_path) {
if (seen_dependency_paths.set(dependency_path) != AK::HashSetResult::InsertedNewEntry)
return;
dependency_paths.append(dependency_path);
};
for (auto const& path : paths) {
auto file = TRY(Core::MappedFile::map(path, Core::MappedFile::Mode::ReadOnly));
lexical_paths.empend(path);
auto& lexical_path = lexical_paths.last();
auto import_base_paths = base_paths;
if (import_base_paths.is_empty())
import_base_paths.append(lexical_path.dirname());
auto module = IDL::Parser::parse(path, file->bytes(), move(import_base_paths), context);
VERIFY(module.interface.has_value());
auto& interface = module.interface.value();
interfaces.append(&interface);
append_dependency_path(path);
for (auto const& imported_file : module.imported_files)
append_dependency_path(imported_file);
}
for (size_t i = 0; i < paths.size(); ++i) {
auto const& lexical_path = lexical_paths[i];
auto& namespace_ = lexical_path.parts_view().at(lexical_path.parts_view().size() - 2);
auto& interface = *interfaces[i];
// 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_)) {
StringBuilder builder;
builder.append(namespace_);
builder.append("::"sv);
builder.append(interface.implemented_name);
interface.fully_qualified_name = builder.to_byte_string();
} else {
interface.fully_qualified_name = interface.implemented_name;
}
if constexpr (BINDINGS_GENERATOR_DEBUG)
interface.dump();
auto path_prefix = LexicalPath::join(output_path, lexical_path.basename(LexicalPath::StripExtension::Yes));
auto header_path = ByteString::formatted("{}.h", path_prefix);
auto implementation_path = ByteString::formatted("{}.cpp", path_prefix);
TRY(write_if_changed(&IDL::generate_header, interface, header_path));
TRY(write_if_changed(&IDL::generate_implementation, interface, implementation_path));
output_files.append(header_path);
output_files.append(implementation_path);
}
if (!depfile_path.is_empty()) {
TRY(generate_depfile(depfile_path, dependency_paths, output_files));
}
return 0;
}