Optionally include documentation in GDExtension API dump

This commit is contained in:
Ricardo Buring 2023-09-25 22:22:06 +02:00
parent fcbc50ec14
commit 8ee04c5f87
3 changed files with 234 additions and 8 deletions

View File

@ -39,6 +39,7 @@
#include "core/version.h" #include "core/version.h"
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
#include "editor/editor_help.h"
static String get_builtin_or_variant_type_name(const Variant::Type p_type) { static String get_builtin_or_variant_type_name(const Variant::Type p_type) {
if (p_type == Variant::NIL) { if (p_type == Variant::NIL) {
@ -88,7 +89,16 @@ static String get_type_meta_name(const GodotTypeInfo::Metadata metadata) {
return argmeta[metadata]; return argmeta[metadata];
} }
Dictionary GDExtensionAPIDump::generate_extension_api() { static String fix_doc_description(const String &p_bbcode) {
// Based on what EditorHelp does.
return p_bbcode.dedent()
.replace("\t", "")
.replace("\r", "")
.strip_edges();
}
Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
Dictionary api_dump; Dictionary api_dump;
{ {
@ -460,12 +470,22 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
api_dump["builtin_class_member_offsets"] = core_type_member_offsets; api_dump["builtin_class_member_offsets"] = core_type_member_offsets;
} }
if (p_include_docs) {
EditorHelp::generate_doc(false);
}
{ {
// Global enums and constants. // Global enums and constants.
Array constants; Array constants;
HashMap<String, List<Pair<String, int64_t>>> enum_list; HashMap<String, List<Pair<String, int64_t>>> enum_list;
HashMap<String, bool> enum_is_bitfield; HashMap<String, bool> enum_is_bitfield;
const DocData::ClassDoc *global_scope_doc = nullptr;
if (p_include_docs) {
global_scope_doc = EditorHelp::get_doc_data()->class_list.getptr("@GlobalScope");
CRASH_COND_MSG(!global_scope_doc, "Could not find '@GlobalScope' in DocData.");
}
for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) {
int64_t value = CoreConstants::get_global_constant_value(i); int64_t value = CoreConstants::get_global_constant_value(i);
String enum_name = CoreConstants::get_global_constant_enum(i); String enum_name = CoreConstants::get_global_constant_enum(i);
@ -479,6 +499,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d["name"] = name; d["name"] = name;
d["value"] = value; d["value"] = value;
d["is_bitfield"] = bitfield; d["is_bitfield"] = bitfield;
if (p_include_docs) {
for (const DocData::ConstantDoc &constant_doc : global_scope_doc->constants) {
if (constant_doc.name == name) {
d["documentation"] = fix_doc_description(constant_doc.description);
break;
}
}
}
constants.push_back(d); constants.push_back(d);
} }
} }
@ -490,11 +518,25 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
Dictionary d1; Dictionary d1;
d1["name"] = E.key; d1["name"] = E.key;
d1["is_bitfield"] = enum_is_bitfield[E.key]; d1["is_bitfield"] = enum_is_bitfield[E.key];
if (p_include_docs) {
const DocData::EnumDoc *enum_doc = global_scope_doc->enums.getptr(E.key);
if (enum_doc) {
d1["documentation"] = fix_doc_description(enum_doc->description);
}
}
Array values; Array values;
for (const Pair<String, int64_t> &F : E.value) { for (const Pair<String, int64_t> &F : E.value) {
Dictionary d2; Dictionary d2;
d2["name"] = F.first; d2["name"] = F.first;
d2["value"] = F.second; d2["value"] = F.second;
if (p_include_docs) {
for (const DocData::ConstantDoc &constant_doc : global_scope_doc->constants) {
if (constant_doc.name == F.first) {
d2["documentation"] = fix_doc_description(constant_doc.description);
break;
}
}
}
values.push_back(d2); values.push_back(d2);
} }
d1["values"] = values; d1["values"] = values;
@ -509,6 +551,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
List<StringName> utility_func_names; List<StringName> utility_func_names;
Variant::get_utility_function_list(&utility_func_names); Variant::get_utility_function_list(&utility_func_names);
const DocData::ClassDoc *global_scope_doc = nullptr;
if (p_include_docs) {
global_scope_doc = EditorHelp::get_doc_data()->class_list.getptr("@GlobalScope");
CRASH_COND_MSG(!global_scope_doc, "Could not find '@GlobalScope' in DocData.");
}
for (const StringName &name : utility_func_names) { for (const StringName &name : utility_func_names) {
Dictionary func; Dictionary func;
func["name"] = String(name); func["name"] = String(name);
@ -545,6 +593,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
func["arguments"] = arguments; func["arguments"] = arguments;
} }
if (p_include_docs) {
for (const DocData::MethodDoc &method_doc : global_scope_doc->methods) {
if (method_doc.name == name) {
func["documentation"] = fix_doc_description(method_doc.description);
break;
}
}
}
utility_funcs.push_back(func); utility_funcs.push_back(func);
} }
@ -571,6 +628,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d["is_keyed"] = Variant::is_keyed(type); d["is_keyed"] = Variant::is_keyed(type);
DocData::ClassDoc *builtin_doc = nullptr;
if (p_include_docs && d["name"] != "Nil") {
builtin_doc = EditorHelp::get_doc_data()->class_list.getptr(d["name"]);
CRASH_COND_MSG(!builtin_doc, vformat("Could not find '%s' in DocData.", d["name"]));
}
{ {
//members //members
Array members; Array members;
@ -581,6 +644,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
Dictionary d2; Dictionary d2;
d2["name"] = String(member_name); d2["name"] = String(member_name);
d2["type"] = get_builtin_or_variant_type_name(Variant::get_member_type(type, member_name)); d2["type"] = get_builtin_or_variant_type_name(Variant::get_member_type(type, member_name));
if (p_include_docs) {
for (const DocData::PropertyDoc &property_doc : builtin_doc->properties) {
if (property_doc.name == member_name) {
d2["documentation"] = fix_doc_description(property_doc.description);
break;
}
}
}
members.push_back(d2); members.push_back(d2);
} }
if (members.size()) { if (members.size()) {
@ -599,6 +670,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
Variant constant = Variant::get_constant_value(type, constant_name); Variant constant = Variant::get_constant_value(type, constant_name);
d2["type"] = get_builtin_or_variant_type_name(constant.get_type()); d2["type"] = get_builtin_or_variant_type_name(constant.get_type());
d2["value"] = constant.get_construct_string(); d2["value"] = constant.get_construct_string();
if (p_include_docs) {
for (const DocData::ConstantDoc &constant_doc : builtin_doc->constants) {
if (constant_doc.name == constant_name) {
d2["documentation"] = fix_doc_description(constant_doc.description);
break;
}
}
}
constants.push_back(d2); constants.push_back(d2);
} }
if (constants.size()) { if (constants.size()) {
@ -624,9 +703,24 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
Dictionary values_dict; Dictionary values_dict;
values_dict["name"] = String(enumeration); values_dict["name"] = String(enumeration);
values_dict["value"] = Variant::get_enum_value(type, enum_name, enumeration); values_dict["value"] = Variant::get_enum_value(type, enum_name, enumeration);
if (p_include_docs) {
for (const DocData::ConstantDoc &constant_doc : builtin_doc->constants) {
if (constant_doc.name == enumeration) {
values_dict["documentation"] = fix_doc_description(constant_doc.description);
break;
}
}
}
values.push_back(values_dict); values.push_back(values_dict);
} }
if (p_include_docs) {
const DocData::EnumDoc *enum_doc = builtin_doc->enums.getptr(enum_name);
if (enum_doc) {
enum_dict["documentation"] = fix_doc_description(enum_doc->description);
}
}
if (values.size()) { if (values.size()) {
enum_dict["values"] = values; enum_dict["values"] = values;
} }
@ -646,11 +740,22 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
Variant::Type rt = Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j)); Variant::Type rt = Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j));
if (rt != Variant::NIL) { if (rt != Variant::NIL) {
Dictionary d2; Dictionary d2;
d2["name"] = Variant::get_operator_name(Variant::Operator(k)); String operator_name = Variant::get_operator_name(Variant::Operator(k));
d2["name"] = operator_name;
if (k != Variant::OP_NEGATE && k != Variant::OP_POSITIVE && k != Variant::OP_NOT && k != Variant::OP_BIT_NEGATE) { if (k != Variant::OP_NEGATE && k != Variant::OP_POSITIVE && k != Variant::OP_NOT && k != Variant::OP_BIT_NEGATE) {
d2["right_type"] = get_builtin_or_variant_type_name(Variant::Type(j)); d2["right_type"] = get_builtin_or_variant_type_name(Variant::Type(j));
} }
d2["return_type"] = get_builtin_or_variant_type_name(Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j))); d2["return_type"] = get_builtin_or_variant_type_name(Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j)));
if (p_include_docs && builtin_doc != nullptr) {
for (const DocData::MethodDoc &operator_doc : builtin_doc->operators) {
if (operator_doc.name == "operator " + operator_name) {
d2["documentation"] = fix_doc_description(operator_doc.description);
break;
}
}
}
operators.push_back(d2); operators.push_back(d2);
} }
} }
@ -697,6 +802,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d2["arguments"] = arguments; d2["arguments"] = arguments;
} }
if (p_include_docs) {
for (const DocData::MethodDoc &method_doc : builtin_doc->methods) {
if (method_doc.name == method_name) {
d2["documentation"] = fix_doc_description(method_doc.description);
break;
}
}
}
methods.push_back(d2); methods.push_back(d2);
} }
if (methods.size()) { if (methods.size()) {
@ -722,6 +836,28 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
if (arguments.size()) { if (arguments.size()) {
d2["arguments"] = arguments; d2["arguments"] = arguments;
} }
if (p_include_docs && builtin_doc) {
for (const DocData::MethodDoc &constructor_doc : builtin_doc->constructors) {
if (constructor_doc.arguments.size() != argcount) {
continue;
}
bool constructor_found = true;
for (int k = 0; k < argcount; k++) {
const DocData::ArgumentDoc &argument_doc = constructor_doc.arguments[k];
const Dictionary &argument_dict = arguments[k];
const String &argument_string = argument_dict["type"];
if (argument_doc.type != argument_string) {
constructor_found = false;
break;
}
}
if (constructor_found) {
d2["documentation"] = fix_doc_description(constructor_doc.description);
}
}
}
constructors.push_back(d2); constructors.push_back(d2);
} }
@ -734,6 +870,10 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d["has_destructor"] = Variant::has_destructor(type); d["has_destructor"] = Variant::has_destructor(type);
} }
if (p_include_docs && builtin_doc != nullptr) {
d["documentation"] = fix_doc_description(builtin_doc->description);
}
builtins.push_back(d); builtins.push_back(d);
} }
@ -763,6 +903,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d["inherits"] = String(parent_class); d["inherits"] = String(parent_class);
} }
DocData::ClassDoc *class_doc = nullptr;
if (p_include_docs) {
class_doc = EditorHelp::get_doc_data()->class_list.getptr(class_name);
CRASH_COND_MSG(!class_doc, vformat("Could not find '%s' in DocData.", class_name));
}
{ {
ClassDB::APIType api = ClassDB::get_api_type(class_name); ClassDB::APIType api = ClassDB::get_api_type(class_name);
static const char *api_type[5] = { "core", "editor", "extension", "editor_extension" }; static const char *api_type[5] = { "core", "editor", "extension", "editor_extension" };
@ -784,6 +930,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d2["name"] = String(F); d2["name"] = String(F);
d2["value"] = ClassDB::get_integer_constant(class_name, F); d2["value"] = ClassDB::get_integer_constant(class_name, F);
if (p_include_docs) {
for (const DocData::ConstantDoc &constant_doc : class_doc->constants) {
if (constant_doc.name == F) {
d2["documentation"] = fix_doc_description(constant_doc.description);
break;
}
}
}
constants.push_back(d2); constants.push_back(d2);
} }
@ -808,11 +963,28 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
Dictionary d3; Dictionary d3;
d3["name"] = String(G->get()); d3["name"] = String(G->get());
d3["value"] = ClassDB::get_integer_constant(class_name, G->get()); d3["value"] = ClassDB::get_integer_constant(class_name, G->get());
if (p_include_docs) {
for (const DocData::ConstantDoc &constant_doc : class_doc->constants) {
if (constant_doc.name == G->get()) {
d3["documentation"] = fix_doc_description(constant_doc.description);
break;
}
}
}
values.push_back(d3); values.push_back(d3);
} }
d2["values"] = values; d2["values"] = values;
if (p_include_docs) {
const DocData::EnumDoc *enum_doc = class_doc->enums.getptr(F);
if (enum_doc) {
d2["documentation"] = fix_doc_description(enum_doc->description);
}
}
enums.push_back(d2); enums.push_back(d2);
} }
@ -864,6 +1036,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d2["arguments"] = arguments; d2["arguments"] = arguments;
} }
if (p_include_docs) {
for (const DocData::MethodDoc &method_doc : class_doc->methods) {
if (method_doc.name == method_name) {
d2["documentation"] = fix_doc_description(method_doc.description);
break;
}
}
}
methods.push_back(d2); methods.push_back(d2);
} else if (F.name.begins_with("_")) { } else if (F.name.begins_with("_")) {
@ -932,6 +1113,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d2["arguments"] = arguments; d2["arguments"] = arguments;
} }
if (p_include_docs) {
for (const DocData::MethodDoc &method_doc : class_doc->methods) {
if (method_doc.name == method_name) {
d2["documentation"] = fix_doc_description(method_doc.description);
break;
}
}
}
methods.push_back(d2); methods.push_back(d2);
} }
} }
@ -966,6 +1156,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d2["arguments"] = arguments; d2["arguments"] = arguments;
} }
if (p_include_docs) {
for (const DocData::MethodDoc &signal_doc : class_doc->signals) {
if (signal_doc.name == signal_name) {
d2["documentation"] = fix_doc_description(signal_doc.description);
break;
}
}
}
signals.push_back(d2); signals.push_back(d2);
} }
@ -1005,6 +1204,16 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
if (index != -1) { if (index != -1) {
d2["index"] = index; d2["index"] = index;
} }
if (p_include_docs) {
for (const DocData::PropertyDoc &property_doc : class_doc->properties) {
if (property_doc.name == property_name) {
d2["documentation"] = fix_doc_description(property_doc.description);
break;
}
}
}
properties.push_back(d2); properties.push_back(d2);
} }
@ -1013,6 +1222,10 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
} }
} }
if (p_include_docs && class_doc != nullptr) {
d["documentation"] = fix_doc_description(class_doc->description);
}
classes.push_back(d); classes.push_back(d);
} }
@ -1065,8 +1278,8 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
return api_dump; return api_dump;
} }
void GDExtensionAPIDump::generate_extension_json_file(const String &p_path) { void GDExtensionAPIDump::generate_extension_json_file(const String &p_path, bool p_include_docs) {
Dictionary api = generate_extension_api(); Dictionary api = generate_extension_api(p_include_docs);
Ref<JSON> json; Ref<JSON> json;
json.instantiate(); json.instantiate();

View File

@ -37,8 +37,8 @@
class GDExtensionAPIDump { class GDExtensionAPIDump {
public: public:
static Dictionary generate_extension_api(); static Dictionary generate_extension_api(bool p_include_docs = false);
static void generate_extension_json_file(const String &p_path); static void generate_extension_json_file(const String &p_path, bool p_include_docs = false);
static Error validate_extension_json_file(const String &p_path); static Error validate_extension_json_file(const String &p_path);
}; };
#endif #endif

View File

@ -224,6 +224,7 @@ static bool print_fps = false;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
static bool dump_gdextension_interface = false; static bool dump_gdextension_interface = false;
static bool dump_extension_api = false; static bool dump_extension_api = false;
static bool include_docs_in_extension_api_dump = false;
static bool validate_extension_api = false; static bool validate_extension_api = false;
static String validate_extension_api_file; static String validate_extension_api_file;
#endif #endif
@ -513,7 +514,8 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n"); OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n");
OS::get_singleton()->print(" --dump-gdextension-interface Generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension.\n"); OS::get_singleton()->print(" --dump-gdextension-interface Generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension.\n");
OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n"); OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n");
OS::get_singleton()->print(" --validate-extension-api <path> Validate an extension API file dumped (with the option above) from a previous version of the engine to ensure API compatibility. If incompatibilities or errors are detected, the return code will be non zero.\n"); OS::get_singleton()->print(" --dump-extension-api-with-docs Generate JSON dump of the Godot API like the previous option, but including documentation.\n");
OS::get_singleton()->print(" --validate-extension-api <path> Validate an extension API file dumped (with one of the two previous options) from a previous version of the engine to ensure API compatibility. If incompatibilities or errors are detected, the return code will be non zero.\n");
OS::get_singleton()->print(" --benchmark Benchmark the run time and print it to console.\n"); OS::get_singleton()->print(" --benchmark Benchmark the run time and print it to console.\n");
OS::get_singleton()->print(" --benchmark-file <path> Benchmark the run time and save it to a given file in JSON format. The path should be absolute.\n"); OS::get_singleton()->print(" --benchmark-file <path> Benchmark the run time and save it to a given file in JSON format. The path should be absolute.\n");
#ifdef TESTS_ENABLED #ifdef TESTS_ENABLED
@ -1249,6 +1251,17 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// run the project instead of a cmdline tool. // run the project instead of a cmdline tool.
// Needs full refactoring to fix properly. // Needs full refactoring to fix properly.
main_args.push_back(I->get()); main_args.push_back(I->get());
} else if (I->get() == "--dump-extension-api-with-docs") {
// Register as an editor instance to use low-end fallback if relevant.
editor = true;
cmdline_tool = true;
dump_extension_api = true;
include_docs_in_extension_api_dump = true;
print_line("Dumping Extension API including documentation");
// Hack. Not needed but otherwise we end up detecting that this should
// run the project instead of a cmdline tool.
// Needs full refactoring to fix properly.
main_args.push_back(I->get());
} else if (I->get() == "--validate-extension-api") { } else if (I->get() == "--validate-extension-api") {
// Register as an editor instance to use low-end fallback if relevant. // Register as an editor instance to use low-end fallback if relevant.
editor = true; editor = true;
@ -2912,7 +2925,7 @@ bool Main::start() {
} }
if (dump_extension_api) { if (dump_extension_api) {
GDExtensionAPIDump::generate_extension_json_file("extension_api.json"); GDExtensionAPIDump::generate_extension_json_file("extension_api.json", include_docs_in_extension_api_dump);
} }
if (dump_gdextension_interface || dump_extension_api) { if (dump_gdextension_interface || dump_extension_api) {