/* * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2021, Luke Wilde * Copyright (c) 2022, Ali Mohammad Pur * Copyright (c) 2023, Andrew Kaster * * SPDX-License-Identifier: BSD-2-Clause */ #include "IDLGenerators.h" #include #include #include #include #include #include #include #include #include template static ErrorOr 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 generate_depfile(StringView depfile_path, ReadonlySpan dependency_paths, ReadonlySpan 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 ladybird_main(Main::Arguments arguments) { Core::ArgsParser args_parser; Vector paths; Vector 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 dependency_paths; HashTable seen_dependency_paths; Vector output_files; Vector lexical_paths; Vector 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 (auto& interface : context.owned_interfaces) assign_fully_qualified_name(*interface); for (size_t i = 0; i < paths.size(); ++i) { auto const& lexical_path = lexical_paths[i]; auto& interface = *interfaces[i]; 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; }