main.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /*
  2. * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  5. * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
  6. * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include "IDLGenerators.h"
  11. #include "Namespaces.h"
  12. #include <AK/Debug.h>
  13. #include <AK/LexicalPath.h>
  14. #include <LibCore/ArgsParser.h>
  15. #include <LibCore/File.h>
  16. #include <LibIDL/IDLParser.h>
  17. #include <LibIDL/Types.h>
  18. ErrorOr<int> serenity_main(Main::Arguments arguments)
  19. {
  20. Core::ArgsParser args_parser;
  21. StringView path;
  22. StringView import_base_path;
  23. StringView output_path = "-"sv;
  24. StringView depfile_path;
  25. StringView depfile_prefix;
  26. args_parser.add_option(Core::ArgsParser::Option {
  27. .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
  28. .help_string = "Add a header search path passed to the compiler",
  29. .long_name = "header-include-path",
  30. .short_name = 'i',
  31. .value_name = "path",
  32. .accept_value = [&](StringView s) {
  33. IDL::g_header_search_paths.append(s);
  34. return true;
  35. },
  36. });
  37. args_parser.add_option(output_path, "Path to output generated files into", "output-path", 'o', "output-path");
  38. args_parser.add_option(depfile_path, "Path to write dependency file to", "depfile", 'd', "depfile-path");
  39. args_parser.add_option(depfile_prefix, "Prefix to prepend to relative paths in dependency file", "depfile-prefix", 'p', "depfile-prefix");
  40. args_parser.add_positional_argument(path, "IDL file", "idl-file");
  41. args_parser.add_positional_argument(import_base_path, "Import base path", "import-base-path", Core::ArgsParser::Required::No);
  42. args_parser.parse(arguments);
  43. auto idl_file = TRY(Core::File::open(path, Core::File::OpenMode::Read));
  44. LexicalPath lexical_path(path);
  45. auto& namespace_ = lexical_path.parts_view().at(lexical_path.parts_view().size() - 2);
  46. auto data = TRY(idl_file->read_until_eof());
  47. if (import_base_path.is_null())
  48. import_base_path = lexical_path.dirname();
  49. IDL::Parser parser(path, data, import_base_path);
  50. auto& interface = parser.parse();
  51. // If the interface name is the same as its namespace, qualify the name in the generated code.
  52. // e.g. Selection::Selection
  53. if (IDL::libweb_interface_namespaces.span().contains_slow(namespace_)) {
  54. StringBuilder builder;
  55. builder.append(namespace_);
  56. builder.append("::"sv);
  57. builder.append(interface.implemented_name);
  58. interface.fully_qualified_name = builder.to_byte_string();
  59. } else {
  60. interface.fully_qualified_name = interface.implemented_name;
  61. }
  62. if constexpr (BINDINGS_GENERATOR_DEBUG) {
  63. dbgln("Attributes:");
  64. for (auto& attribute : interface.attributes) {
  65. dbgln(" {}{}{}{} {}",
  66. attribute.inherit ? "inherit " : "",
  67. attribute.readonly ? "readonly " : "",
  68. attribute.type->name(),
  69. attribute.type->is_nullable() ? "?" : "",
  70. attribute.name);
  71. }
  72. dbgln("Functions:");
  73. for (auto& function : interface.functions) {
  74. dbgln(" {}{} {}",
  75. function.return_type->name(),
  76. function.return_type->is_nullable() ? "?" : "",
  77. function.name);
  78. for (auto& parameter : function.parameters) {
  79. dbgln(" {}{} {}",
  80. parameter.type->name(),
  81. parameter.type->is_nullable() ? "?" : "",
  82. parameter.name);
  83. }
  84. }
  85. dbgln("Static Functions:");
  86. for (auto& function : interface.static_functions) {
  87. dbgln(" static {}{} {}",
  88. function.return_type->name(),
  89. function.return_type->is_nullable() ? "?" : "",
  90. function.name);
  91. for (auto& parameter : function.parameters) {
  92. dbgln(" {}{} {}",
  93. parameter.type->name(),
  94. parameter.type->is_nullable() ? "?" : "",
  95. parameter.name);
  96. }
  97. }
  98. }
  99. StringBuilder output_builder;
  100. auto write_if_changed = [&](auto generator_function, StringView file_path) -> ErrorOr<void> {
  101. (*generator_function)(interface, output_builder);
  102. auto current_file_or_error = Core::File::open(file_path, Core::File::OpenMode::Read);
  103. if (current_file_or_error.is_error() && current_file_or_error.error().code() != ENOENT)
  104. return current_file_or_error.release_error();
  105. ByteBuffer current_contents;
  106. if (!current_file_or_error.is_error())
  107. current_contents = TRY(current_file_or_error.value()->read_until_eof());
  108. // Only write to disk if contents have changed
  109. if (current_contents != output_builder.string_view().bytes()) {
  110. auto output_file = TRY(Core::File::open(file_path, Core::File::OpenMode::Write | Core::File::OpenMode::Truncate));
  111. TRY(output_file->write_until_depleted(output_builder.string_view().bytes()));
  112. }
  113. // FIXME: Can we add clear_with_capacity to StringBuilder instead of throwing away the allocated buffer?
  114. output_builder.clear();
  115. return {};
  116. };
  117. String namespace_header;
  118. String namespace_implementation;
  119. String constructor_header;
  120. String constructor_implementation;
  121. String prototype_header;
  122. String prototype_implementation;
  123. String iterator_prototype_header;
  124. String iterator_prototype_implementation;
  125. String global_mixin_header;
  126. String global_mixin_implementation;
  127. auto path_prefix = LexicalPath::join(output_path, lexical_path.basename(LexicalPath::StripExtension::Yes));
  128. if (interface.is_namespace) {
  129. namespace_header = TRY(String::formatted("{}Namespace.h", path_prefix));
  130. namespace_implementation = TRY(String::formatted("{}Namespace.cpp", path_prefix));
  131. TRY(write_if_changed(&IDL::generate_namespace_header, namespace_header));
  132. TRY(write_if_changed(&IDL::generate_namespace_implementation, namespace_implementation));
  133. } else {
  134. constructor_header = TRY(String::formatted("{}Constructor.h", path_prefix));
  135. constructor_implementation = TRY(String::formatted("{}Constructor.cpp", path_prefix));
  136. prototype_header = TRY(String::formatted("{}Prototype.h", path_prefix));
  137. prototype_implementation = TRY(String::formatted("{}Prototype.cpp", path_prefix));
  138. TRY(write_if_changed(&IDL::generate_constructor_header, constructor_header));
  139. TRY(write_if_changed(&IDL::generate_constructor_implementation, constructor_implementation));
  140. TRY(write_if_changed(&IDL::generate_prototype_header, prototype_header));
  141. TRY(write_if_changed(&IDL::generate_prototype_implementation, prototype_implementation));
  142. }
  143. if (interface.pair_iterator_types.has_value()) {
  144. iterator_prototype_header = TRY(String::formatted("{}IteratorPrototype.h", path_prefix));
  145. iterator_prototype_implementation = TRY(String::formatted("{}IteratorPrototype.cpp", path_prefix));
  146. TRY(write_if_changed(&IDL::generate_iterator_prototype_header, iterator_prototype_header));
  147. TRY(write_if_changed(&IDL::generate_iterator_prototype_implementation, iterator_prototype_implementation));
  148. }
  149. if (interface.extended_attributes.contains("Global")) {
  150. global_mixin_header = TRY(String::formatted("{}GlobalMixin.h", path_prefix));
  151. global_mixin_implementation = TRY(String::formatted("{}GlobalMixin.cpp", path_prefix));
  152. TRY(write_if_changed(&IDL::generate_global_mixin_header, global_mixin_header));
  153. TRY(write_if_changed(&IDL::generate_global_mixin_implementation, global_mixin_implementation));
  154. }
  155. if (!depfile_path.is_empty()) {
  156. auto depfile = TRY(Core::File::open_file_or_standard_stream(depfile_path, Core::File::OpenMode::Write));
  157. StringBuilder depfile_builder;
  158. 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 }) {
  159. if (s.is_empty())
  160. continue;
  161. if (!depfile_prefix.is_empty())
  162. depfile_builder.append(LexicalPath::join(depfile_prefix, s).string());
  163. else
  164. depfile_builder.append(s);
  165. break;
  166. }
  167. depfile_builder.append(':');
  168. for (auto const& path : parser.imported_files()) {
  169. depfile_builder.append(" \\\n "sv);
  170. depfile_builder.append(path);
  171. }
  172. depfile_builder.append('\n');
  173. TRY(depfile->write_until_depleted(depfile_builder.string_view().bytes()));
  174. }
  175. return 0;
  176. }