Replace the bindings generator's up-front dependency include scan with a GeneratedIncludes accumulator that is threaded through the implementation, dictionary conversion, argument conversion, and wrapping emitters. Emitters now add the headers required by the code they produce, while generated headers and implementations are built into temporary builders so includes can be collected before the prologue is emitted. This also folds module, bindings, and iterator include handling into the same sorted/deduped header path mechanism.
175 lines
6.3 KiB
C++
175 lines
6.3 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 <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, typename Input>
|
|
static ErrorOr<void> write_if_changed(GeneratorFunction generator_function, Input const& input, StringView file_path)
|
|
{
|
|
StringBuilder output_builder;
|
|
generator_function(input, 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();
|
|
|
|
bool file_exists = !current_file_or_error.is_error();
|
|
ByteBuffer current_contents;
|
|
if (file_exists)
|
|
current_contents = TRY(current_file_or_error.value()->read_until_eof());
|
|
|
|
if (!file_exists || 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());
|
|
}
|
|
|
|
static ByteString cpp_namespace_for_module_path(ByteString const& module_own_path)
|
|
{
|
|
auto path = LexicalPath { module_own_path };
|
|
auto parts = path.parts_view();
|
|
for (size_t i = 0; i + 2 < parts.size(); ++i) {
|
|
if (parts[i] != "LibWeb"sv)
|
|
continue;
|
|
|
|
return parts[i + 1].to_byte_string();
|
|
}
|
|
|
|
if (parts.size() >= 2)
|
|
return parts[parts.size() - 2].to_byte_string();
|
|
|
|
return {};
|
|
}
|
|
|
|
static void assign_fully_qualified_name(IDL::Interface& interface)
|
|
{
|
|
auto namespace_name = cpp_namespace_for_module_path(interface.module_own_path);
|
|
if (namespace_name.is_empty()) {
|
|
interface.fully_qualified_name = interface.implemented_name;
|
|
return;
|
|
}
|
|
|
|
interface.fully_qualified_name = ByteString::formatted("{}::{}", namespace_name, interface.implemented_name);
|
|
}
|
|
|
|
ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|
{
|
|
Core::ArgsParser args_parser;
|
|
Vector<ByteString> 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(LexicalPath::canonicalized_path(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;
|
|
|
|
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));
|
|
auto module = IDL::Parser::parse(path, file->bytes(), context);
|
|
append_dependency_path(path);
|
|
}
|
|
|
|
context.resolve();
|
|
|
|
for (auto& interface : context.owned_interfaces)
|
|
assign_fully_qualified_name(*interface);
|
|
|
|
for (auto const& module : context.owned_modules) {
|
|
auto lexical_path = LexicalPath { module->module_own_path };
|
|
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);
|
|
|
|
if constexpr (BINDINGS_GENERATOR_DEBUG) {
|
|
if (module->interface.has_value())
|
|
module->interface->dump();
|
|
}
|
|
|
|
TRY(write_if_changed(&IDL::generate_header, *module, header_path));
|
|
TRY(write_if_changed(&IDL::generate_implementation, *module, 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;
|
|
}
|