Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix loading GDExtension dependencies on Android #88381

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions core/extension/gdextension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,41 @@ String GDExtension::get_extension_list_config_file() {
return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg");
}

Vector<SharedObject> GDExtension::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) {
Vector<SharedObject> dependencies_shared_objects;
if (p_config->has_section("dependencies")) {
List<String> config_dependencies;
p_config->get_section_keys("dependencies", &config_dependencies);

for (const String &dependency : config_dependencies) {
Vector<String> dependency_tags = dependency.split(".");
bool all_tags_met = true;
for (int i = 0; i < dependency_tags.size(); i++) {
String tag = dependency_tags[i].strip_edges();
if (!p_has_feature(tag)) {
all_tags_met = false;
break;
}
}

if (all_tags_met) {
Dictionary dependency_value = p_config->get_value("dependencies", dependency);
for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) {
String dependency_path = *key;
String target_path = dependency_value[*key];
if (dependency_path.is_relative_path()) {
dependency_path = p_path.get_base_dir().path_join(dependency_path);
}
dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path));
}
break;
}
}
}

return dependencies_shared_objects;
}

String GDExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) {
// First, check the explicit libraries.
if (p_config->has_section("libraries")) {
Expand Down Expand Up @@ -727,10 +762,24 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String
return *function;
}

Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol) {
Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies) {
String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path);

Vector<String> abs_dependencies_paths;
if (p_dependencies != nullptr && !p_dependencies->is_empty()) {
for (const SharedObject &dependency : *p_dependencies) {
abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path));
}
}

String actual_lib_path;
Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, true, &actual_lib_path, Engine::get_singleton()->is_editor_hint());
OS::GDExtensionData data = {
true, // also_set_library_path
&actual_lib_path, // r_resolved_path
Engine::get_singleton()->is_editor_hint(), // generate_temp_files
&abs_dependencies_paths, // library_dependencies
};
Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data);

if (actual_lib_path.get_file() != abs_path.get_file()) {
// If temporary files are generated, let's change the library path to point at the original,
Expand Down Expand Up @@ -970,7 +1019,8 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path,
FileAccess::get_modified_time(library_path));
#endif

err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol);
Vector<SharedObject> library_dependencies = GDExtension::find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol, &library_dependencies);
if (err != OK) {
// Unreference the extension so that this loading can be considered a failure.
p_extension.unref();
Expand Down
4 changes: 3 additions & 1 deletion core/extension/gdextension.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "core/io/config_file.h"
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
#include "core/os/shared_object.h"

class GDExtensionMethodBind;

Expand Down Expand Up @@ -123,8 +124,9 @@ class GDExtension : public Resource {

static String get_extension_list_config_file();
static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature);

Error open_library(const String &p_path, const String &p_entry_symbol);
Error open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies = nullptr);
void close_library();

enum InitializationLevel {
Expand Down
9 changes: 8 additions & 1 deletion core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,14 @@ class OS {

virtual void alert(const String &p_alert, const String &p_title = "ALERT!");

virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) { return ERR_UNAVAILABLE; }
struct GDExtensionData {
bool also_set_library_path = false;
String *r_resolved_path = nullptr;
bool generate_temp_files = false;
PackedStringArray *library_dependencies = nullptr;
};

virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) { return ERR_UNAVAILABLE; }
virtual Error close_dynamic_library(void *p_library_handle) { return ERR_UNAVAILABLE; }
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) { return ERR_UNAVAILABLE; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**************************************************************************/
/* editor_export_shared_object.h */
/* shared_object.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
Expand Down Expand Up @@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#ifndef EDITOR_EXPORT_SHARED_OBJECT_H
#define EDITOR_EXPORT_SHARED_OBJECT_H
#ifndef SHARED_OBJECT_H
#define SHARED_OBJECT_H

#include "core/string/ustring.h"
#include "core/templates/vector.h"
Expand All @@ -48,4 +48,4 @@ struct SharedObject {
SharedObject() {}
};

#endif // EDITOR_EXPORT_SHARED_OBJECT_H
#endif // SHARED_OBJECT_H
6 changes: 3 additions & 3 deletions drivers/unix/os_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ String OS_Unix::get_locale() const {
return locale;
}

Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path;

if (FileAccess::exists(path) && path.is_relative_path()) {
Expand All @@ -808,8 +808,8 @@ Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handl
p_library_handle = dlopen(path.utf8().get_data(), GODOT_DLOPEN_MODE);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));

if (r_resolved_path != nullptr) {
*r_resolved_path = path;
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
*p_data->r_resolved_path = path;
}

return OK;
Expand Down
2 changes: 1 addition & 1 deletion drivers/unix/os_unix.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class OS_Unix : public OS {

virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override;

virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;

Expand Down
2 changes: 1 addition & 1 deletion editor/export/editor_export_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ struct EditorProgress;

#include "core/io/dir_access.h"
#include "core/io/zip_io.h"
#include "core/os/shared_object.h"
#include "editor_export_preset.h"
#include "editor_export_shared_object.h"
#include "scene/gui/rich_text_label.h"
#include "scene/main/node.h"
#include "scene/resources/image_texture.h"
Expand Down
4 changes: 4 additions & 0 deletions editor/export/editor_export_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ void EditorExportPlugin::add_shared_object(const String &p_path, const Vector<St
shared_objects.push_back(SharedObject(p_path, p_tags, p_target));
}

void EditorExportPlugin::_add_shared_object(const SharedObject &p_shared_object) {
shared_objects.push_back(p_shared_object);
}

void EditorExportPlugin::add_ios_framework(const String &p_path) {
ios_frameworks.push_back(p_path);
}
Expand Down
3 changes: 2 additions & 1 deletion editor/export/editor_export_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
#define EDITOR_EXPORT_PLUGIN_H

#include "core/extension/gdextension.h"
#include "core/os/shared_object.h"
#include "editor_export_platform.h"
#include "editor_export_preset.h"
#include "editor_export_shared_object.h"
#include "scene/main/node.h"

class EditorExportPlugin : public RefCounted {
Expand Down Expand Up @@ -94,6 +94,7 @@ class EditorExportPlugin : public RefCounted {

void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap);
void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String());
void _add_shared_object(const SharedObject &p_shared_object);

void add_ios_framework(const String &p_path);
void add_ios_embedded_framework(const String &p_path);
Expand Down
31 changes: 3 additions & 28 deletions editor/plugins/gdextension_export_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,34 +129,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector)));
}

List<String> dependencies;
if (config->has_section("dependencies")) {
config->get_section_keys("dependencies", &dependencies);
}

for (const String &E : dependencies) {
Vector<String> dependency_tags = E.split(".");
bool all_tags_met = true;
for (int i = 0; i < dependency_tags.size(); i++) {
String tag = dependency_tags[i].strip_edges();
if (!p_features.has(tag)) {
all_tags_met = false;
break;
}
}

if (all_tags_met) {
Dictionary dependency = config->get_value("dependencies", E);
for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) {
String dependency_path = *key;
String target_path = dependency[*key];
if (dependency_path.is_relative_path()) {
dependency_path = p_path.get_base_dir().path_join(dependency_path);
}
add_shared_object(dependency_path, dependency_tags, target_path);
}
break;
}
Vector<SharedObject> dependencies_shared_objects = GDExtension::find_extension_dependencies(p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); });
for (const SharedObject &shared_object : dependencies_shared_objects) {
_add_shared_object(shared_object);
}
}
}
Expand Down
72 changes: 56 additions & 16 deletions platform/android/os_android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,39 @@ Vector<String> OS_Android::get_granted_permissions() const {
return godot_java->get_granted_permissions();
}

Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
bool OS_Android::copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path) {
if (!FileAccess::exists(p_library_path)) {
return false;
}

Ref<DirAccess> da_ref = DirAccess::create_for_path(p_library_path);
if (!da_ref.is_valid()) {
return false;
}

String copy_path = p_target_dir.path_join(p_library_path.get_file());
bool copy_exists = FileAccess::exists(copy_path);
if (copy_exists) {
print_verbose("Deleting existing library copy " + copy_path);
if (da_ref->remove(copy_path) != OK) {
print_verbose("Unable to delete " + copy_path);
}
}

print_verbose("Copying " + p_library_path + " to " + p_target_dir);
Error create_dir_result = da_ref->make_dir_recursive(p_target_dir);
if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) {
copy_exists = da_ref->copy(p_library_path, copy_path) == OK;
}

if (copy_exists && r_copy_path != nullptr) {
*r_copy_path = copy_path;
}

return copy_exists;
}

Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path;
bool so_file_exists = true;
if (!FileAccess::exists(path)) {
Expand All @@ -172,24 +204,32 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha

p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
if (!p_library_handle && so_file_exists) {
// The library may be on the sdcard and thus inaccessible. Try to copy it to the internal
// directory.
uint64_t so_modified_time = FileAccess::get_modified_time(p_path);
String dynamic_library_path = get_dynamic_libraries_path().path_join(String::num_uint64(so_modified_time));
String internal_path = dynamic_library_path.path_join(p_path.get_file());

bool internal_so_file_exists = FileAccess::exists(internal_path);
if (!internal_so_file_exists) {
Ref<DirAccess> da_ref = DirAccess::create_for_path(p_path);
if (da_ref.is_valid()) {
Error create_dir_result = da_ref->make_dir_recursive(dynamic_library_path);
if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) {
internal_so_file_exists = da_ref->copy(path, internal_path) == OK;
// The library (and its dependencies) may be on the sdcard and thus inaccessible.
// Try to copy to the internal directory for access.
const String dynamic_library_path = get_dynamic_libraries_path();

if (p_data != nullptr && p_data->library_dependencies != nullptr && !p_data->library_dependencies->is_empty()) {
// Copy the library dependencies
print_verbose("Copying library dependencies..");
for (const String &library_dependency_path : *p_data->library_dependencies) {
String internal_library_dependency_path;
if (!copy_dynamic_library(library_dependency_path, dynamic_library_path.path_join(library_dependency_path.get_base_dir()), &internal_library_dependency_path)) {
ERR_PRINT(vformat("Unable to copy library dependency %s", library_dependency_path));
} else {
void *lib_dependency_handle = dlopen(internal_library_dependency_path.utf8().get_data(), RTLD_NOW);
if (!lib_dependency_handle) {
ERR_PRINT(vformat("Can't open dynamic library dependency: %s. Error: %s.", internal_library_dependency_path, dlerror()));
}
}
}
}

String internal_path;
print_verbose("Copying library " + p_path);
const bool internal_so_file_exists = copy_dynamic_library(p_path, dynamic_library_path.path_join(p_path.get_base_dir()), &internal_path);

if (internal_so_file_exists) {
print_verbose("Opening library " + internal_path);
p_library_handle = dlopen(internal_path.utf8().get_data(), RTLD_NOW);
if (p_library_handle) {
path = internal_path;
Expand All @@ -199,8 +239,8 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha

ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));

if (r_resolved_path != nullptr) {
*r_resolved_path = path;
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
*p_data->r_resolved_path = path;
}

return OK;
Expand Down
4 changes: 3 additions & 1 deletion platform/android/os_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class OS_Android : public OS_Unix {

virtual void alert(const String &p_alert, const String &p_title) override;

virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;

virtual String get_name() const override;
virtual String get_distribution_name() const override;
Expand Down Expand Up @@ -178,6 +178,8 @@ class OS_Android : public OS_Unix {
private:
// Location where we relocate external dynamic libraries to make them accessible.
String get_dynamic_libraries_path() const;
// Copy a dynamic library to the given location to make it accessible for loading.
bool copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path = nullptr);
};

#endif // OS_ANDROID_H
2 changes: 1 addition & 1 deletion platform/ios/os_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class OS_IOS : public OS_Unix {
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;

virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;

Expand Down
10 changes: 5 additions & 5 deletions platform/ios/os_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -217,13 +217,13 @@ void register_dynamic_symbol(char *name, void *address) {
return p_path;
}

Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
if (p_path.length() == 0) {
// Static xcframework.
p_library_handle = RTLD_SELF;

if (r_resolved_path != nullptr) {
*r_resolved_path = p_path;
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
*p_data->r_resolved_path = p_path;
}

return OK;
Expand Down Expand Up @@ -256,8 +256,8 @@ void register_dynamic_symbol(char *name, void *address) {
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));

if (r_resolved_path != nullptr) {
*r_resolved_path = path;
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
*p_data->r_resolved_path = path;
}

return OK;
Expand Down
Loading
Loading