diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/gdnative/gdnative.cpp | 20 | ||||
-rw-r--r-- | modules/gdnative/gdnative_library_editor_plugin.cpp | 16 | ||||
-rw-r--r-- | modules/gdnative/register_types.cpp | 108 | ||||
-rw-r--r-- | modules/gdscript/editor/gdscript_translation_parser_plugin.cpp | 178 | ||||
-rw-r--r-- | modules/gdscript/editor/gdscript_translation_parser_plugin.h | 57 | ||||
-rw-r--r-- | modules/gdscript/register_types.cpp | 6 |
6 files changed, 347 insertions, 38 deletions
diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index 3d747ba41e..bb2da70c3a 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -291,8 +291,26 @@ bool GDNative::initialize() { return false; } #ifdef IPHONE_ENABLED - // on iOS we use static linking + // On iOS we use static linking by default. String path = ""; + + // On iOS dylibs is not allowed, but can be replaced with .framework or .xcframework. + // If they are used, we can run dlopen on them. + // They should be located under Frameworks directory, so we need to replace library path. + if (!lib_path.ends_with(".a")) { + path = ProjectSettings::get_singleton()->globalize_path(lib_path); + + if (!FileAccess::exists(path)) { + String lib_name = lib_path.get_basename().get_file(); + String framework_path_format = "Frameworks/$name.framework/$name"; + + Dictionary format_dict; + format_dict["name"] = lib_name; + String framework_path = framework_path_format.format(format_dict, "$_"); + + path = OS::get_singleton()->get_executable_path().get_base_dir().plus_file(framework_path); + } + } #elif defined(ANDROID_ENABLED) // On Android dynamic libraries are located separately from resource assets, // we should pass library name to dlopen(). The library name is flattened diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index 5896da4640..1d4d188f23 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -127,15 +127,24 @@ void GDNativeLibraryEditor::_on_item_button(Object *item, int column, int id) { String section = (id == BUTTON_SELECT_DEPENDENCES || id == BUTTON_CLEAR_DEPENDENCES) ? "dependencies" : "entry"; if (id == BUTTON_SELECT_LIBRARY || id == BUTTON_SELECT_DEPENDENCES) { + TreeItem *treeItem = Object::cast_to<TreeItem>(item)->get_parent(); EditorFileDialog::FileMode mode = EditorFileDialog::FILE_MODE_OPEN_FILE; if (id == BUTTON_SELECT_DEPENDENCES) { mode = EditorFileDialog::FILE_MODE_OPEN_FILES; + } else if (treeItem->get_text(0) == "iOS") { + mode = EditorFileDialog::FILE_MODE_OPEN_ANY; } file_dialog->set_meta("target", target); file_dialog->set_meta("section", section); file_dialog->clear_filters(); - file_dialog->add_filter(Object::cast_to<TreeItem>(item)->get_parent()->get_metadata(0)); + + String filter_string = treeItem->get_metadata(0); + Vector<String> filters = filter_string.split(",", false, 0); + for (int i = 0; i < filters.size(); i++) { + file_dialog->add_filter(filters[i]); + } + file_dialog->set_file_mode(mode); file_dialog->popup_centered_ratio(); @@ -309,7 +318,9 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { platform_ios.name = "iOS"; platform_ios.entries.push_back("armv7"); platform_ios.entries.push_back("arm64"); - platform_ios.library_extension = "*.dylib"; + // iOS can use both Static and Dynamic libraries. + // Frameworks is actually a folder with files. + platform_ios.library_extension = "*.framework; Framework, *.xcframework; Binary Framework, *.a; Static Library, *.dylib; Dynamic Library"; platforms["iOS"] = platform_ios; } @@ -360,6 +371,7 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { //file_dialog->set_resizable(true); add_child(file_dialog); file_dialog->connect("file_selected", callable_mp(this, &GDNativeLibraryEditor::_on_library_selected)); + file_dialog->connect("dir_selected", callable_mp(this, &GDNativeLibraryEditor::_on_library_selected)); file_dialog->connect("files_selected", callable_mp(this, &GDNativeLibraryEditor::_on_dependencies_selected)); new_architecture_dialog = memnew(ConfirmationDialog); diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 136af5bd1e..d1b1513ac3 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -142,47 +142,85 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty } } + // Add symbols for staticaly linked libraries on iOS if (p_features.has("iOS")) { - // Register symbols in the "fake" dynamic lookup table, because dlsym does not work well on iOS. - LibrarySymbol expected_symbols[] = { - { "gdnative_init", true }, - { "gdnative_terminate", false }, - { "nativescript_init", false }, - { "nativescript_frame", false }, - { "nativescript_thread_enter", false }, - { "nativescript_thread_exit", false }, - { "gdnative_singleton", false } - }; - String declare_pattern = "extern \"C\" void $name(void)$weak;\n"; - String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n"; - String linker_flags = ""; - for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { - String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; - String code = declare_pattern.replace("$name", full_name); - code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))"); - additional_code += code; - - if (!expected_symbols[i].is_required) { - if (linker_flags.length() > 0) { - linker_flags += " "; + bool should_fake_dynamic = false; + + List<String> entry_keys; + config->get_section_keys("entry", &entry_keys); + + for (List<String>::Element *E = entry_keys.front(); E; E = E->next()) { + String key = E->get(); + + Vector<String> tags = key.split("."); + + bool skip = false; + for (int i = 0; i < tags.size(); i++) { + bool has_feature = p_features.has(tags[i]); + + if (!has_feature) { + skip = true; + break; } - linker_flags += "-Wl,-U,_" + full_name; } - } - additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix()); - String register_pattern = " if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n"; - for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { - String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; - additional_code += register_pattern.replace("$name", full_name); + if (skip) { + continue; + } + + String entry_lib_path = config->get_value("entry", key); + if (entry_lib_path.begins_with("res://") && entry_lib_path.ends_with(".a")) { + // If we find static library that was used for export + // we should add a fake loopup table. + // In case of dynamic library being used, + // this symbols will not cause any issues with library loading. + should_fake_dynamic = true; + break; + } } - additional_code += "}\n"; - additional_code += String("struct $prefixstruct {$prefixstruct() {add_ios_init_callback($prefixinit);}};\n").replace("$prefix", lib->get_symbol_prefix()); - additional_code += String("$prefixstruct $prefixstruct_instance;\n").replace("$prefix", lib->get_symbol_prefix()); - add_ios_cpp_code(additional_code); - add_ios_linker_flags(linker_flags); + if (should_fake_dynamic) { + // Register symbols in the "fake" dynamic lookup table, because dlsym does not work well on iOS. + LibrarySymbol expected_symbols[] = { + { "gdnative_init", true }, + { "gdnative_terminate", false }, + { "nativescript_init", false }, + { "nativescript_frame", false }, + { "nativescript_thread_enter", false }, + { "nativescript_thread_exit", false }, + { "gdnative_singleton", false } + }; + String declare_pattern = "extern \"C\" void $name(void)$weak;\n"; + String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" + "extern void add_ios_init_callback(void (*cb)());\n"; + String linker_flags = ""; + for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; + String code = declare_pattern.replace("$name", full_name); + code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))"); + additional_code += code; + + if (!expected_symbols[i].is_required) { + if (linker_flags.length() > 0) { + linker_flags += " "; + } + linker_flags += "-Wl,-U,_" + full_name; + } + } + + additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix()); + String register_pattern = " if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n"; + for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; + additional_code += register_pattern.replace("$name", full_name); + } + additional_code += "}\n"; + additional_code += String("struct $prefixstruct {$prefixstruct() {add_ios_init_callback($prefixinit);}};\n").replace("$prefix", lib->get_symbol_prefix()); + additional_code += String("$prefixstruct $prefixstruct_instance;\n").replace("$prefix", lib->get_symbol_prefix()); + + add_ios_cpp_code(additional_code); + add_ios_linker_flags(linker_flags); + } } } diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp new file mode 100644 index 0000000000..a1b18978fc --- /dev/null +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -0,0 +1,178 @@ +/*************************************************************************/ +/* gdscript_translation_parser_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_translation_parser_plugin.h" + +#include "core/io/resource_loader.h" +#include "modules/gdscript/gdscript.h" + +void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const { + GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions); +} + +Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_extracted_strings) { + List<String> extensions; + get_recognized_extensions(&extensions); + bool extension_valid = false; + for (auto E = extensions.front(); E; E = E->next()) { + if (p_path.get_extension() == E->get()) { + extension_valid = true; + break; + } + } + + if (!extension_valid) { + Vector<String> temp; + for (auto E = extensions.front(); E; E = E->next()) { + temp.push_back(E->get()); + } + String valid_extensions = String(", ").join(temp); + ERR_PRINT("Argument p_path \"" + p_path + "\" has wrong extension. List of valid extensions: " + valid_extensions); + return ERR_INVALID_PARAMETER; + } + + Error err; + RES loaded_res = ResourceLoader::load(p_path, "", false, &err); + if (err) { + ERR_PRINT("Failed to load " + p_path); + return err; + } + + Ref<GDScript> gdscript = loaded_res; + parse_text(gdscript->get_source_code(), r_extracted_strings); + + return OK; +} + +void GDScriptEditorTranslationParserPlugin::parse_text(const String &p_text, Vector<String> *r_extracted_strings) { + // Parse and match all GDScript function API that involves translation string. + // E.g get_node("Label").text = "something", var test = tr("something"), "something" will be matched and collected. + + Vector<String> parsed_strings; + + // Search translation strings with RegEx. + regex.clear(); + regex.compile(String("|").join(patterns)); + Array results = regex.search_all(p_text); + _get_captured_strings(results, &parsed_strings); + + // Special handling for FileDialog. + Vector<String> temp; + _parse_file_dialog(p_text, &temp); + parsed_strings.append_array(temp); + + // Filter out / and + + String filter = "(?:\\\\\\n|\"[\\s\\\\]*\\+\\s*\")"; + regex.clear(); + regex.compile(filter); + for (int i = 0; i < parsed_strings.size(); i++) { + parsed_strings.set(i, regex.sub(parsed_strings[i], "", true)); + } + + r_extracted_strings->append_array(parsed_strings); +} + +void GDScriptEditorTranslationParserPlugin::_parse_file_dialog(const String &p_source_code, Vector<String> *r_output) { + // FileDialog API has the form .filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]). + // First filter: Get "*.png ; PNG Images", "*.gd ; GDScript Files" from PackedStringArray. + regex.clear(); + regex.compile(String("|").join(file_dialog_patterns)); + Array results = regex.search_all(p_source_code); + + Vector<String> temp; + _get_captured_strings(results, &temp); + String captured_strings = String(",").join(temp); + + // Second filter: Get the texts after semicolon from "*.png ; PNG Images","*.gd ; GDScript Files". + String second_filter = "\"[^;]+;" + text + "\""; + regex.clear(); + regex.compile(second_filter); + results = regex.search_all(captured_strings); + _get_captured_strings(results, r_output); + for (int i = 0; i < r_output->size(); i++) { + r_output->set(i, r_output->get(i).strip_edges()); + } +} + +void GDScriptEditorTranslationParserPlugin::_get_captured_strings(const Array &p_results, Vector<String> *r_output) { + Ref<RegExMatch> result; + for (int i = 0; i < p_results.size(); i++) { + result = p_results[i]; + for (int j = 0; j < result->get_group_count(); j++) { + String s = result->get_string(j + 1); + // Prevent reading text with only spaces. + if (!s.strip_edges().empty()) { + r_output->push_back(s); + } + } + } +} + +GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() { + // Regex search pattern templates. + // The extra complication in the regex pattern is to ensure that the matching works when users write over multiple lines, use tabs etc. + const String dot = "\\.[\\s\\\\]*"; + const String str_assign_template = "[\\s\\\\]*=[\\s\\\\]*\"" + text + "\""; + const String first_arg_template = "[\\s\\\\]*\\([\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)"; + const String second_arg_template = "[\\s\\\\]*\\([\\s\\S]+?,[\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)"; + + // Common patterns. + patterns.push_back("tr" + first_arg_template); + patterns.push_back(dot + "text" + str_assign_template); + patterns.push_back(dot + "placeholder_text" + str_assign_template); + patterns.push_back(dot + "hint_tooltip" + str_assign_template); + patterns.push_back(dot + "set_text" + first_arg_template); + patterns.push_back(dot + "set_tooltip" + first_arg_template); + patterns.push_back(dot + "set_placeholder" + first_arg_template); + + // Tabs and TabContainer API. + patterns.push_back(dot + "set_tab_title" + second_arg_template); + patterns.push_back(dot + "add_tab" + first_arg_template); + + // PopupMenu API. + patterns.push_back(dot + "add_check_item" + first_arg_template); + patterns.push_back(dot + "add_icon_check_item" + second_arg_template); + patterns.push_back(dot + "add_icon_item" + second_arg_template); + patterns.push_back(dot + "add_icon_radio_check_item" + second_arg_template); + patterns.push_back(dot + "add_item" + first_arg_template); + patterns.push_back(dot + "add_multistate_item" + first_arg_template); + patterns.push_back(dot + "add_radio_check_item" + first_arg_template); + patterns.push_back(dot + "add_separator" + first_arg_template); + patterns.push_back(dot + "add_submenu_item" + first_arg_template); + patterns.push_back(dot + "set_item_text" + second_arg_template); + //patterns.push_back(dot + "set_item_tooltip" + second_arg_template); //no tr() behind this function. might be bug. + + // FileDialog API - special case. + const String fd_text = "((?:[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*\"[\\s\\\\]*,?)*)"; + const String packed_string_array = "[\\s\\\\]*PackedStringArray[\\s\\\\]*\\([\\s\\\\]*\\[" + fd_text + "\\][\\s\\\\]*\\)"; + file_dialog_patterns.push_back(dot + "add_filter[\\s\\\\]*\\(" + fd_text + "[\\s\\\\]*\\)"); + file_dialog_patterns.push_back(dot + "filters[\\s\\\\]*=" + packed_string_array); + file_dialog_patterns.push_back(dot + "set_filters[\\s\\\\]*\\(" + packed_string_array + "[\\s\\\\]*\\)"); +} diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h new file mode 100644 index 0000000000..ef967845b9 --- /dev/null +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -0,0 +1,57 @@ +/*************************************************************************/ +/* gdscript_translation_parser_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H +#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H + +#include "editor/editor_translation_parser.h" +#include "modules/regex/regex.h" + +class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin { + GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin); + + // Regex and search patterns that are used to match translation strings. + const String text = "((?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*)"; + RegEx regex; + Vector<String> patterns; + Vector<String> file_dialog_patterns; + + void _parse_file_dialog(const String &p_source_code, Vector<String> *r_output); + void _get_captured_strings(const Array &p_results, Vector<String> *r_output); + +public: + virtual Error parse_file(const String &p_path, Vector<String> *r_extracted_strings); + virtual void parse_text(const String &p_text, Vector<String> *r_extracted_strings); + virtual void get_recognized_extensions(List<String> *r_extensions) const; + + GDScriptEditorTranslationParserPlugin(); +}; + +#endif // GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 0625123530..53e760ffa7 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -46,7 +46,9 @@ Ref<ResourceFormatSaverGDScript> resource_saver_gd; #include "editor/editor_export.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/editor_translation_parser.h" #include "editor/gdscript_highlighter.h" +#include "editor/gdscript_translation_parser_plugin.h" #ifndef GDSCRIPT_NO_LSP #include "core/engine.h" @@ -164,6 +166,10 @@ void register_gdscript_types() { #ifdef TOOLS_ENABLED ScriptEditor::register_create_syntax_highlighter_function(GDScriptSyntaxHighlighter::create); EditorNode::add_init_callback(_editor_init); + + Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin; + gdscript_translation_parser_plugin.instance(); + EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD); #endif // TOOLS_ENABLED } |