| 1 | /**************************************************************************/ |
| 2 | /* gdextension_export_plugin.h */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #ifndef GDEXTENSION_EXPORT_PLUGIN_H |
| 32 | #define GDEXTENSION_EXPORT_PLUGIN_H |
| 33 | |
| 34 | #include "editor/export/editor_export.h" |
| 35 | |
| 36 | class GDExtensionExportPlugin : public EditorExportPlugin { |
| 37 | protected: |
| 38 | virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features); |
| 39 | virtual String get_name() const { return "GDExtension" ; } |
| 40 | }; |
| 41 | |
| 42 | void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { |
| 43 | if (p_type != "GDExtension" ) { |
| 44 | return; |
| 45 | } |
| 46 | |
| 47 | Ref<ConfigFile> config; |
| 48 | config.instantiate(); |
| 49 | |
| 50 | Error err = config->load(p_path); |
| 51 | ERR_FAIL_COND_MSG(err, "Failed to load GDExtension file: " + p_path); |
| 52 | |
| 53 | // Check whether this GDExtension should be exported. |
| 54 | bool android_aar_plugin = config->get_value("configuration" , "android_aar_plugin" , false); |
| 55 | if (android_aar_plugin && p_features.has("android" )) { |
| 56 | // The gdextension configuration and Android .so files will be provided by the Android aar |
| 57 | // plugin it's part of, so we abort here. |
| 58 | skip(); |
| 59 | return; |
| 60 | } |
| 61 | |
| 62 | ERR_FAIL_COND_MSG(!config->has_section_key("configuration" , "entry_symbol" ), "Failed to export GDExtension file, missing entry symbol: " + p_path); |
| 63 | |
| 64 | String entry_symbol = config->get_value("configuration" , "entry_symbol" ); |
| 65 | |
| 66 | HashSet<String> all_archs; |
| 67 | all_archs.insert("x86_32" ); |
| 68 | all_archs.insert("x86_64" ); |
| 69 | all_archs.insert("arm32" ); |
| 70 | all_archs.insert("arm64" ); |
| 71 | all_archs.insert("rv64" ); |
| 72 | all_archs.insert("ppc32" ); |
| 73 | all_archs.insert("ppc64" ); |
| 74 | all_archs.insert("wasm32" ); |
| 75 | all_archs.insert("universal" ); |
| 76 | |
| 77 | HashSet<String> archs; |
| 78 | HashSet<String> features_wo_arch; |
| 79 | for (const String &tag : p_features) { |
| 80 | if (all_archs.has(tag)) { |
| 81 | archs.insert(tag); |
| 82 | } else { |
| 83 | features_wo_arch.insert(tag); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | if (archs.is_empty()) { |
| 88 | archs.insert("unknown_arch" ); // Not archs specified, still try to match. |
| 89 | } |
| 90 | |
| 91 | for (const String &arch_tag : archs) { |
| 92 | PackedStringArray tags; |
| 93 | String library_path = GDExtension::find_extension_library( |
| 94 | p_path, config, [features_wo_arch, arch_tag](String p_feature) { return features_wo_arch.has(p_feature) || (p_feature == arch_tag); }, &tags); |
| 95 | if (!library_path.is_empty()) { |
| 96 | add_shared_object(library_path, tags); |
| 97 | |
| 98 | if (p_features.has("iOS" ) && (library_path.ends_with(".a" ) || library_path.ends_with(".xcframework" ))) { |
| 99 | String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" |
| 100 | "extern void add_ios_init_callback(void (*cb)());\n" |
| 101 | "\n" |
| 102 | "extern \"C\" void $ENTRY();\n" |
| 103 | "void $ENTRY_init() {\n" |
| 104 | " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" |
| 105 | "}\n" |
| 106 | "struct $ENTRY_struct {\n" |
| 107 | " $ENTRY_struct() {\n" |
| 108 | " add_ios_init_callback($ENTRY_init);\n" |
| 109 | " }\n" |
| 110 | "};\n" |
| 111 | "$ENTRY_struct $ENTRY_struct_instance;\n\n" ; |
| 112 | additional_code = additional_code.replace("$ENTRY" , entry_symbol); |
| 113 | add_ios_cpp_code(additional_code); |
| 114 | |
| 115 | String linker_flags = "-Wl,-U,_" + entry_symbol; |
| 116 | add_ios_linker_flags(linker_flags); |
| 117 | } |
| 118 | } else { |
| 119 | Vector<String> features_vector; |
| 120 | for (const String &E : p_features) { |
| 121 | features_vector.append(E); |
| 122 | } |
| 123 | ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s" , p_path, String(", " ).join(features_vector))); |
| 124 | } |
| 125 | |
| 126 | List<String> dependencies; |
| 127 | if (config->has_section("dependencies" )) { |
| 128 | config->get_section_keys("dependencies" , &dependencies); |
| 129 | } |
| 130 | |
| 131 | for (const String &E : dependencies) { |
| 132 | Vector<String> dependency_tags = E.split("." ); |
| 133 | bool all_tags_met = true; |
| 134 | for (int i = 0; i < dependency_tags.size(); i++) { |
| 135 | String tag = dependency_tags[i].strip_edges(); |
| 136 | if (!p_features.has(tag)) { |
| 137 | all_tags_met = false; |
| 138 | break; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | if (all_tags_met) { |
| 143 | Dictionary dependency = config->get_value("dependencies" , E); |
| 144 | for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { |
| 145 | String dependency_path = *key; |
| 146 | String target_path = dependency[*key]; |
| 147 | if (dependency_path.is_relative_path()) { |
| 148 | dependency_path = p_path.get_base_dir().path_join(dependency_path); |
| 149 | } |
| 150 | add_shared_object(dependency_path, dependency_tags, target_path); |
| 151 | } |
| 152 | break; |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | #endif // GDEXTENSION_EXPORT_PLUGIN_H |
| 159 | |