/* * 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 "Namespaces.h" #include #include #include #include #include #include extern Vector s_header_search_paths; namespace IDL { void generate_namespace_header(IDL::Interface const&, StringBuilder&); void generate_namespace_implementation(IDL::Interface const&, StringBuilder&); void generate_constructor_header(IDL::Interface const&, StringBuilder&); void generate_constructor_implementation(IDL::Interface const&, StringBuilder&); void generate_prototype_header(IDL::Interface const&, StringBuilder&); void generate_prototype_implementation(IDL::Interface const&, StringBuilder&); void generate_iterator_prototype_header(IDL::Interface const&, StringBuilder&); void generate_iterator_prototype_implementation(IDL::Interface const&, StringBuilder&); void generate_global_mixin_header(IDL::Interface const&, StringBuilder&); void generate_global_mixin_implementation(IDL::Interface const&, StringBuilder&); } ErrorOr serenity_main(Main::Arguments arguments) { Core::ArgsParser args_parser; StringView path; StringView import_base_path; StringView output_path = "-"sv; StringView depfile_path; StringView depfile_prefix; 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) { s_header_search_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_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(import_base_path, "Import base path", "import-base-path", Core::ArgsParser::Required::No); args_parser.parse(arguments); auto idl_file = TRY(Core::File::open(path, Core::File::OpenMode::Read)); LexicalPath lexical_path(path); auto& namespace_ = lexical_path.parts_view().at(lexical_path.parts_view().size() - 2); auto data = TRY(idl_file->read_until_eof()); if (import_base_path.is_null()) import_base_path = lexical_path.dirname(); IDL::Parser parser(path, data, import_base_path); 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_)) { StringBuilder builder; builder.append(namespace_); builder.append("::"sv); builder.append(interface.name); interface.fully_qualified_name = builder.to_deprecated_string(); } else { interface.fully_qualified_name = interface.name; } if constexpr (BINDINGS_GENERATOR_DEBUG) { dbgln("Attributes:"); for (auto& attribute : interface.attributes) { dbgln(" {}{}{}{} {}", attribute.inherit ? "inherit " : "", attribute.readonly ? "readonly " : "", attribute.type->name(), attribute.type->is_nullable() ? "?" : "", attribute.name); } dbgln("Functions:"); for (auto& function : interface.functions) { dbgln(" {}{} {}", function.return_type->name(), function.return_type->is_nullable() ? "?" : "", function.name); for (auto& parameter : function.parameters) { dbgln(" {}{} {}", parameter.type->name(), parameter.type->is_nullable() ? "?" : "", parameter.name); } } dbgln("Static Functions:"); for (auto& function : interface.static_functions) { dbgln(" static {}{} {}", function.return_type->name(), function.return_type->is_nullable() ? "?" : "", function.name); for (auto& parameter : function.parameters) { dbgln(" {}{} {}", parameter.type->name(), parameter.type->is_nullable() ? "?" : "", parameter.name); } } } StringBuilder output_builder; auto write_if_changed = [&](auto generator_function, StringView file_path) -> ErrorOr { (*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().bytes()) 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 (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)); 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 (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(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_empty()) { auto depfile = TRY(Core::File::open_file_or_standard_stream(depfile_path, Core::File::OpenMode::Write)); StringBuilder depfile_builder; 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()) continue; if (!depfile_prefix.is_empty()) depfile_builder.append(LexicalPath::join(depfile_prefix, s).string()); else depfile_builder.append(s); break; } depfile_builder.append(':'); for (auto const& path : parser.imported_files()) { depfile_builder.append(" \\\n "sv); depfile_builder.append(path); } depfile_builder.append('\n'); TRY(depfile->write_until_depleted(depfile_builder.string_view().bytes())); } return 0; }