diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index e47c9ea54361..763c0fb3275e 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -245,6 +245,10 @@ class DirAccessPack : public DirAccess { uint64_t get_space_left(); + virtual bool is_link(String p_file) { return false; } + virtual String read_link(String p_file) { return p_file; } + virtual Error create_link(String p_source, String p_target) { return FAILED; } + virtual String get_filesystem_type() const; DirAccessPack(); diff --git a/core/os/dir_access.cpp b/core/os/dir_access.cpp index b7c3a17ba996..39ae475c1245 100644 --- a/core/os/dir_access.cpp +++ b/core/os/dir_access.cpp @@ -331,7 +331,7 @@ class DirChanger { } }; -Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags) { +Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags, bool p_copy_links) { List dirs; String curdir = get_current_dir(); @@ -339,7 +339,9 @@ Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flag String n = get_next(); while (n != String()) { if (n != "." && n != "..") { - if (current_is_dir()) { + if (p_copy_links && is_link(get_current_dir().plus_file(n))) { + create_link(read_link(get_current_dir().plus_file(n)), p_to + n); + } else if (current_is_dir()) { dirs.push_back(n); } else { const String &rel_path = n; @@ -371,7 +373,7 @@ Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flag Error err = change_dir(E->get()); ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + E->get() + "'."); - err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags); + err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links); if (err) { change_dir(".."); ERR_FAIL_V_MSG(err, "Failed to copy recursively."); @@ -383,7 +385,7 @@ Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flag return OK; } -Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags) { +Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags, bool p_copy_links) { ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist."); DirAccess *target_da = DirAccess::create_for_path(p_to); @@ -402,7 +404,7 @@ Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags) { } DirChanger dir_changer(this, p_from); - Error err = _copy_dir(target_da, p_to, p_chmod_flags); + Error err = _copy_dir(target_da, p_to, p_chmod_flags, p_copy_links); memdelete(target_da); return err; diff --git a/core/os/dir_access.h b/core/os/dir_access.h index ec738d30d5f9..16154a4850b8 100644 --- a/core/os/dir_access.h +++ b/core/os/dir_access.h @@ -50,7 +50,7 @@ class DirAccess { AccessType _access_type = ACCESS_FILESYSTEM; static CreateFunc create_func[ACCESS_MAX]; ///< set this to instance a filesystem object - Error _copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags); + Error _copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags, bool p_copy_links); protected: String _get_root_path() const; @@ -89,11 +89,15 @@ class DirAccess { static bool exists(String p_dir); virtual uint64_t get_space_left() = 0; - Error copy_dir(String p_from, String p_to, int p_chmod_flags = -1); + Error copy_dir(String p_from, String p_to, int p_chmod_flags = -1, bool p_copy_links = false); virtual Error copy(String p_from, String p_to, int p_chmod_flags = -1); virtual Error rename(String p_from, String p_to) = 0; virtual Error remove(String p_name) = 0; + virtual bool is_link(String p_file) = 0; + virtual String read_link(String p_file) = 0; + virtual Error create_link(String p_source, String p_target) = 0; + // Meant for editor code when we want to quickly remove a file without custom // handling (e.g. removing a cache file). static void remove_file_or_error(String p_path) { diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index 3323da5db47d..5abe5d2c87ca 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -406,6 +406,53 @@ Error DirAccessUnix::remove(String p_path) { } } +bool DirAccessUnix::is_link(String p_file) { + if (p_file.is_rel_path()) { + p_file = get_current_dir().plus_file(p_file); + } + + p_file = fix_path(p_file); + + struct stat flags; + if ((lstat(p_file.utf8().get_data(), &flags) != 0)) { + return FAILED; + } + + return S_ISLNK(flags.st_mode); +} + +String DirAccessUnix::read_link(String p_file) { + if (p_file.is_rel_path()) { + p_file = get_current_dir().plus_file(p_file); + } + + p_file = fix_path(p_file); + + char buf[256]; + memset(buf, 0, 256); + ssize_t len = readlink(p_file.utf8().get_data(), buf, sizeof(buf)); + String link; + if (len > 0) { + link.parse_utf8(buf, len); + } + return link; +} + +Error DirAccessUnix::create_link(String p_source, String p_target) { + if (p_target.is_rel_path()) { + p_target = get_current_dir().plus_file(p_target); + } + + p_source = fix_path(p_source); + p_target = fix_path(p_target); + + if (symlink(p_source.utf8().get_data(), p_target.utf8().get_data()) == 0) { + return OK; + } else { + return FAILED; + } +} + uint64_t DirAccessUnix::get_space_left() { #ifndef NO_STATVFS struct statvfs vfs; diff --git a/drivers/unix/dir_access_unix.h b/drivers/unix/dir_access_unix.h index 12994a6b7689..28a7053d2599 100644 --- a/drivers/unix/dir_access_unix.h +++ b/drivers/unix/dir_access_unix.h @@ -79,6 +79,10 @@ class DirAccessUnix : public DirAccess { virtual Error rename(String p_path, String p_new_path); virtual Error remove(String p_path); + virtual bool is_link(String p_file); + virtual String read_link(String p_file); + virtual Error create_link(String p_source, String p_target); + virtual uint64_t get_space_left(); virtual String get_filesystem_type() const; diff --git a/drivers/windows/dir_access_windows.h b/drivers/windows/dir_access_windows.h index 553d5c47012d..b151b631e93c 100644 --- a/drivers/windows/dir_access_windows.h +++ b/drivers/windows/dir_access_windows.h @@ -78,6 +78,10 @@ class DirAccessWindows : public DirAccess { virtual Error rename(String p_path, String p_new_path); virtual Error remove(String p_path); + virtual bool is_link(String p_file) { return false; }; + virtual String read_link(String p_file) { return p_file; }; + virtual Error create_link(String p_source, String p_target) { return FAILED; }; + uint64_t get_space_left(); virtual String get_filesystem_type() const; diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index 0de6b27d275a..7c3b76e4d729 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/core_constants.h" #include "core/io/file_access_encrypted.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" @@ -335,9 +336,18 @@ bool GDNative::initialize() { // On OSX the exported libraries are located under the Frameworks directory. // So we need to replace the library path. String path = ProjectSettings::get_singleton()->globalize_path(lib_path); - if (!FileAccess::exists(path)) { + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + if (!da->file_exists(path) && !da->dir_exists(path)) { path = OS::get_singleton()->get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(lib_path.get_file()); } + + if (da->dir_exists(path)) { // Target library is a ".framework", add library base name to the path. + path = path.plus_file(path.get_file().get_basename()); + } + + memdelete(da); + #else String path = ProjectSettings::get_singleton()->globalize_path(lib_path); #endif diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index dfb26c13e31d..b4ac0d886e5f 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -131,7 +131,7 @@ void GDNativeLibraryEditor::_on_item_button(Object *item, int column, int id) { 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") { + } else if (treeItem->get_text(0) == "iOS" || treeItem->get_text(0) == "macOS") { mode = EditorFileDialog::FILE_MODE_OPEN_ANY; } @@ -278,11 +278,10 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { platforms["X11"] = platform_linux; NativePlatformConfig platform_osx; - platform_osx.name = "Mac OSX"; + platform_osx.name = "macOS"; platform_osx.entries.push_back("64"); - platform_osx.entries.push_back("32"); - platform_osx.library_extension = "*.dylib"; - platforms["OSX"] = platform_osx; + platform_osx.library_extension = "*.framework; Framework, *.dylib; Dynamic Library"; + platforms["macOS"] = platform_osx; NativePlatformConfig platform_haiku; platform_haiku.name = "Haiku"; diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index fe87644fe2ed..7b9cbeea478d 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -74,6 +74,10 @@ class DirAccessJAndroid : public DirAccess { virtual Error rename(String p_from, String p_to); virtual Error remove(String p_name); + virtual bool is_link(String p_file) { return false; } + virtual String read_link(String p_file) { return p_file; } + virtual Error create_link(String p_source, String p_target) { return FAILED; } + virtual String get_filesystem_type() const; uint64_t get_space_left(); diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index a7868efaa890..b2f881a8a8c3 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -894,9 +894,22 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p if (err == OK) { DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { - err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); + String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); + if (da->dir_exists(src_path)) { +#ifndef UNIX_ENABLED + WARN_PRINT("Relative symlinks are not supported, exported " + src_path.get_file() + " might be broken!"); +#endif + print_verbose("export framework: " + src_path + " -> " + tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file()); + err = da->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file()); + if (err == OK) { + err = da->copy_dir(src_path, tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(), -1, true); + } + } else { + print_verbose("export dylib: " + src_path + " -> " + tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file()); + err = da->copy(src_path, tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file()); + } if (err == OK && sign_enabled) { - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file(), ent_path); + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(), ent_path); } } memdelete(da); @@ -980,7 +993,48 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String if (f == "." || f == "..") { continue; } - if (da->current_is_dir()) { + if (da->is_link(f)) { + OS::Time time = OS::get_singleton()->get_time(); + OS::Date date = OS::get_singleton()->get_date(); + + zip_fileinfo zipfi; + zipfi.tmz_date.tm_hour = time.hour; + zipfi.tmz_date.tm_mday = date.day; + zipfi.tmz_date.tm_min = time.min; + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_sec = time.sec; + zipfi.tmz_date.tm_year = date.year; + zipfi.dosDate = 0; + // 0120000: symbolic link type + // 0000755: permissions rwxr-xr-x + // 0000644: permissions rw-r--r-- + uint32_t _mode = 0120644; + zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); + zipfi.internal_fa = 0; + + zipOpenNewFileInZip4(p_zip, + p_folder.plus_file(f).utf8().get_data(), + &zipfi, + nullptr, + 0, + nullptr, + 0, + nullptr, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION, + 0, + -MAX_WBITS, + DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, + nullptr, + 0, + 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions + 0); + + String target = da->read_link(f); + zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); + zipCloseFileInZip(p_zip); + } else if (da->current_is_dir()) { _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name); } else { bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name));