[.NET Export] Improve .NET export process.

[macOS export] Fix incorrect file placement, search paths and architecture detection.
[macOS export] Automatically detect executable files and set +x flag.
[macOS export] Automatically apply "Disable Library Validation" entitlements when required.
[macOS export] Remove old Mono export code.
Fix folder tree creation for shared objects export.
Add arch suffix to the exported .NET "data" folder name.
Remove old Mono code from .NET "data" folder lookup.
This commit is contained in:
bruvzg 2022-10-05 20:31:07 +03:00
parent 58ca303141
commit 6daf4c6593
No known key found for this signature in database
GPG Key ID: 7960FCF39844EC38
7 changed files with 156 additions and 266 deletions

View File

@ -144,6 +144,7 @@ public:
};
virtual Ref<EditorExportPreset> create_preset();
virtual bool is_executable(const String &p_path) const { return false; }
virtual void clear_messages() { messages.clear(); }
virtual void add_message(ExportMessageType p_type, const String &p_category, const String &p_message) {

View File

@ -185,10 +185,12 @@ Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset>
String src_path = ProjectSettings::get_singleton()->globalize_path(so_files[i].path);
String target_path;
if (so_files[i].target.is_empty()) {
target_path = p_path.get_base_dir().path_join(src_path.get_file());
target_path = p_path.get_base_dir();
} else {
target_path = p_path.get_base_dir().path_join(so_files[i].target).path_join(src_path.get_file());
target_path = p_path.get_base_dir().path_join(so_files[i].target);
da->make_dir_recursive(target_path);
}
target_path = target_path.path_join(src_path.get_file());
if (da->dir_exists(src_path)) {
err = da->make_dir_recursive(target_path);

View File

@ -17,6 +17,8 @@ namespace GodotTools.Export
{
public partial class ExportPlugin : EditorExportPlugin
{
private List<string> _tempFolders = new List<string>();
public void RegisterExportSettings()
{
// TODO: These would be better as export preset options, but that doesn't seem to be supported yet
@ -111,18 +113,46 @@ namespace GodotTools.Export
string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
// TODO: This works for now, as we only implemented support for x86 family desktop so far, but it needs to be fixed
string arch = features.Contains("x86_64") ? "x86_64" : "x86";
var archs = new List<string>();
if (features.Contains("x86_64"))
{
archs.Add("x86_64");
}
else if (features.Contains("x86_32"))
{
archs.Add("x86_32");
}
else if (features.Contains("arm64"))
{
archs.Add("arm64");
}
else if (features.Contains("universal"))
{
if (platform == OS.Platforms.MacOS)
{
archs.Add("x86_64");
archs.Add("arm64");
}
}
foreach (var arch in archs)
{
string ridOS = DetermineRuntimeIdentifierOS(platform);
string ridArch = DetermineRuntimeIdentifierArch(arch);
string runtimeIdentifier = $"{ridOS}-{ridArch}";
string projectDataDirName = $"{DetermineDataDirNameForProject()}_{arch}";
if (platform == OS.Platforms.MacOS)
{
projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
}
// Create temporary publish output directory
string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
$"{Process.GetCurrentProcess().Id}-{buildConfig}-{runtimeIdentifier}");
_tempFolders.Add(publishOutputTempDir);
if (!Directory.Exists(publishOutputTempDir))
Directory.CreateDirectory(publishOutputTempDir);
@ -149,24 +179,12 @@ namespace GodotTools.Export
"Publish succeeded but project assembly not found in the output directory");
}
// Copy all files from the dotnet publish output directory to
// a data directory next to the Godot output executable.
string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject());
if (Directory.Exists(outputDataDir))
Directory.Delete(outputDataDir, recursive: true); // Clean first
Directory.CreateDirectory(outputDataDir);
foreach (string dir in Directory.GetDirectories(publishOutputTempDir, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(publishOutputTempDir.Length + 1)));
}
// Add to the exported project shared object list.
foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
{
File.Copy(file, Path.Combine(outputDataDir, file.Substring(publishOutputTempDir.Length + 1)));
AddSharedObject(file, tags: null, projectDataDirName);
}
}
}
@ -198,6 +216,12 @@ namespace GodotTools.Export
if (Directory.Exists(aotTempDir))
Directory.Delete(aotTempDir, recursive: true);
foreach (string folder in _tempFolders)
{
Directory.Delete(folder, recursive: true);
}
_tempFolders.Clear();
// TODO: The following is just a workaround until the export plugins can be made to abort with errors
// We check for empty as well, because it's set to empty after hot-reloading

View File

@ -94,138 +94,63 @@ String _get_mono_user_dir() {
class _GodotSharpDirs {
public:
String res_data_dir;
String res_metadata_dir;
String res_config_dir;
String res_temp_dir;
String res_temp_assemblies_base_dir;
String res_temp_assemblies_dir;
String mono_user_dir;
String mono_logs_dir;
String api_assemblies_base_dir;
String api_assemblies_dir;
#ifdef TOOLS_ENABLED
String mono_solutions_dir;
String build_logs_dir;
String data_editor_tools_dir;
#else
// Equivalent of res_assemblies_dir, but in the data directory rather than in 'res://'.
// Only defined on export templates. Used when exporting assemblies outside of PCKs.
String data_game_assemblies_dir;
#endif
String data_mono_etc_dir;
String data_mono_lib_dir;
#ifdef WINDOWS_ENABLED
String data_mono_bin_dir;
#endif
private:
_GodotSharpDirs() {
res_data_dir = ProjectSettings::get_singleton()->get_project_data_path().path_join("mono");
String res_data_dir = ProjectSettings::get_singleton()->get_project_data_path().path_join("mono");
res_metadata_dir = res_data_dir.path_join("metadata");
res_config_dir = res_data_dir.path_join("etc").path_join("mono");
// TODO use paths from csproj
res_temp_dir = res_data_dir.path_join("temp");
res_temp_assemblies_base_dir = res_temp_dir.path_join("bin");
res_temp_assemblies_dir = res_temp_assemblies_base_dir.path_join(_get_expected_build_config());
api_assemblies_base_dir = res_data_dir.path_join("assemblies");
res_temp_assemblies_dir = res_data_dir.path_join("temp").path_join("bin").path_join(_get_expected_build_config());
#ifdef WEB_ENABLED
mono_user_dir = "user://";
#else
mono_user_dir = _get_mono_user_dir();
#endif
mono_logs_dir = mono_user_dir.path_join("mono_logs");
#ifdef TOOLS_ENABLED
mono_solutions_dir = mono_user_dir.path_join("solutions");
build_logs_dir = mono_user_dir.path_join("build_logs");
String base_path = ProjectSettings::get_singleton()->globalize_path("res://");
#endif
String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir();
String res_dir = OS::get_singleton()->get_bundle_resource_dir();
#ifdef TOOLS_ENABLED
String data_dir_root = exe_dir.path_join("GodotSharp");
data_editor_tools_dir = data_dir_root.path_join("Tools");
api_assemblies_base_dir = data_dir_root.path_join("Api");
String data_mono_root_dir = data_dir_root.path_join("Mono");
data_mono_etc_dir = data_mono_root_dir.path_join("etc");
#ifdef ANDROID_ENABLED
data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir();
#else
data_mono_lib_dir = data_mono_root_dir.path_join("lib");
#endif
#ifdef WINDOWS_ENABLED
data_mono_bin_dir = data_mono_root_dir.path_join("bin");
#endif
String api_assemblies_base_dir = data_dir_root.path_join("Api");
build_logs_dir = mono_user_dir.path_join("build_logs");
#ifdef MACOS_ENABLED
if (!DirAccess::exists(data_editor_tools_dir)) {
data_editor_tools_dir = exe_dir.path_join("../Resources/GodotSharp/Tools");
data_editor_tools_dir = res_dir.path_join("GodotSharp").path_join("Tools");
}
if (!DirAccess::exists(api_assemblies_base_dir)) {
api_assemblies_base_dir = exe_dir.path_join("../Resources/GodotSharp/Api");
}
if (!DirAccess::exists(data_mono_root_dir)) {
data_mono_etc_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/etc");
data_mono_lib_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/lib");
api_assemblies_base_dir = res_dir.path_join("GodotSharp").path_join("Api");
}
#endif
#else
api_assemblies_dir = api_assemblies_base_dir.path_join(GDMono::get_expected_api_build_config());
#else // TOOLS_ENABLED
String arch = Engine::get_singleton()->get_architecture_name();
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
String data_dir_root = exe_dir.path_join("data_" + appname_safe);
String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch);
if (!DirAccess::exists(data_dir_root)) {
data_dir_root = exe_dir.path_join("data_Godot");
data_dir_root = exe_dir.path_join("data_Godot_" + arch);
}
String data_mono_root_dir = data_dir_root.path_join("Mono");
data_mono_etc_dir = data_mono_root_dir.path_join("etc");
#ifdef ANDROID_ENABLED
data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir();
#else
data_mono_lib_dir = data_mono_root_dir.path_join("lib");
data_game_assemblies_dir = data_dir_root.path_join("Assemblies");
#endif
#ifdef WINDOWS_ENABLED
data_mono_bin_dir = data_mono_root_dir.path_join("bin");
#endif
#ifdef MACOS_ENABLED
if (!DirAccess::exists(data_mono_root_dir)) {
data_mono_etc_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/etc");
data_mono_lib_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/lib");
if (!DirAccess::exists(data_dir_root)) {
data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + arch);
}
if (!DirAccess::exists(data_game_assemblies_dir)) {
data_game_assemblies_dir = exe_dir.path_join("../Resources/GodotSharp/Assemblies");
if (!DirAccess::exists(data_dir_root)) {
data_dir_root = res_dir.path_join("data_Godot_" + arch);
}
#endif
#endif
#ifdef TOOLS_ENABLED
api_assemblies_dir = api_assemblies_base_dir.path_join(GDMono::get_expected_api_build_config());
#else
api_assemblies_dir = data_dir_root;
#endif
}
@ -237,26 +162,10 @@ public:
}
};
String get_res_data_dir() {
return _GodotSharpDirs::get_singleton().res_data_dir;
}
String get_res_metadata_dir() {
return _GodotSharpDirs::get_singleton().res_metadata_dir;
}
String get_res_config_dir() {
return _GodotSharpDirs::get_singleton().res_config_dir;
}
String get_res_temp_dir() {
return _GodotSharpDirs::get_singleton().res_temp_dir;
}
String get_res_temp_assemblies_base_dir() {
return _GodotSharpDirs::get_singleton().res_temp_assemblies_base_dir;
}
String get_res_temp_assemblies_dir() {
return _GodotSharpDirs::get_singleton().res_temp_assemblies_dir;
}
@ -265,23 +174,11 @@ String get_api_assemblies_dir() {
return _GodotSharpDirs::get_singleton().api_assemblies_dir;
}
String get_api_assemblies_base_dir() {
return _GodotSharpDirs::get_singleton().api_assemblies_base_dir;
}
String get_mono_user_dir() {
return _GodotSharpDirs::get_singleton().mono_user_dir;
}
String get_mono_logs_dir() {
return _GodotSharpDirs::get_singleton().mono_logs_dir;
}
#ifdef TOOLS_ENABLED
String get_mono_solutions_dir() {
return _GodotSharpDirs::get_singleton().mono_solutions_dir;
}
String get_build_logs_dir() {
return _GodotSharpDirs::get_singleton().build_logs_dir;
}
@ -289,23 +186,6 @@ String get_build_logs_dir() {
String get_data_editor_tools_dir() {
return _GodotSharpDirs::get_singleton().data_editor_tools_dir;
}
#else
String get_data_game_assemblies_dir() {
return _GodotSharpDirs::get_singleton().data_game_assemblies_dir;
}
#endif
String get_data_mono_etc_dir() {
return _GodotSharpDirs::get_singleton().data_mono_etc_dir;
}
String get_data_mono_lib_dir() {
return _GodotSharpDirs::get_singleton().data_mono_lib_dir;
}
#ifdef WINDOWS_ENABLED
String get_data_mono_bin_dir() {
return _GodotSharpDirs::get_singleton().data_mono_bin_dir;
}
#endif
} // namespace GodotSharpDirs

View File

@ -35,34 +35,18 @@
namespace GodotSharpDirs {
String get_res_data_dir();
String get_res_metadata_dir();
String get_res_config_dir();
String get_res_temp_dir();
String get_res_temp_assemblies_base_dir();
String get_res_temp_assemblies_dir();
String get_api_assemblies_dir();
String get_api_assemblies_base_dir();
String get_mono_user_dir();
String get_mono_logs_dir();
#ifdef TOOLS_ENABLED
String get_mono_solutions_dir();
String get_build_logs_dir();
String get_data_editor_tools_dir();
#else
String get_data_game_assemblies_dir();
#endif
String get_data_mono_etc_dir();
String get_data_mono_lib_dir();
#ifdef WINDOWS_ENABLED
String get_data_mono_bin_dir();
#endif
} // namespace GodotSharpDirs
#endif // GODOTSHARP_DIRS_H

View File

@ -31,6 +31,8 @@
#include "export_plugin.h"
#include "codesign.h"
#include "lipo.h"
#include "macho.h"
#include "core/string/translation.h"
#include "editor/editor_node.h"
@ -754,6 +756,7 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres
if (extensions_to_sign.is_empty()) {
extensions_to_sign.push_back("dylib");
extensions_to_sign.push_back("framework");
extensions_to_sign.push_back("");
}
Error dir_access_error;
@ -778,6 +781,10 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres
if (code_sign_error != OK) {
return code_sign_error;
}
if (is_executable(current_file_path)) {
// chmod with 0755 if the file is executable.
FileAccess::set_unix_permissions(current_file_path, 0755);
}
} else if (dir_access->current_is_dir()) {
Error code_sign_error{ _code_sign_directory(p_preset, current_file_path, p_ent_path, p_should_error_on_non_code) };
if (code_sign_error != OK) {
@ -799,6 +806,14 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access
const String &p_in_app_path, bool p_sign_enabled,
const Ref<EditorExportPreset> &p_preset, const String &p_ent_path,
bool p_should_error_on_non_code_sign) {
static Vector<String> extensions_to_sign;
if (extensions_to_sign.is_empty()) {
extensions_to_sign.push_back("dylib");
extensions_to_sign.push_back("framework");
extensions_to_sign.push_back("");
}
Error err{ OK };
if (dir_access->dir_exists(p_src_path)) {
#ifndef UNIX_ENABLED
@ -818,8 +833,14 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access
// If it is a directory, find and sign all dynamic libraries.
err = _code_sign_directory(p_preset, p_in_app_path, p_ent_path, p_should_error_on_non_code_sign);
} else {
if (extensions_to_sign.find(p_in_app_path.get_extension()) > -1) {
err = _code_sign(p_preset, p_in_app_path, p_ent_path, false);
}
if (is_executable(p_in_app_path)) {
// chmod with 0755 if the file is executable.
FileAccess::set_unix_permissions(p_in_app_path, 0755);
}
}
}
return err;
}
@ -877,6 +898,17 @@ Error EditorExportPlatformMacOS::_create_dmg(const String &p_dmg_path, const Str
return OK;
}
bool EditorExportPlatformMacOS::is_shbang(const String &p_path) const {
Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("Can't open file: \"%s\".", p_path));
uint16_t magic = fb->get_16();
return (magic == 0x2123);
}
bool EditorExportPlatformMacOS::is_executable(const String &p_path) const {
return MachO::is_macho(p_path) || LipO::is_lipo(p_path) || is_shbang(p_path);
}
Error EditorExportPlatformMacOS::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
if (f.is_null()) {
@ -1158,11 +1190,8 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
// Now process our template.
bool found_binary = false;
Vector<String> dylibs_found;
while (ret == UNZ_OK && err == OK) {
bool is_execute = false;
// Get filename.
unz_file_info info;
char fname[16384];
@ -1219,7 +1248,6 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
continue; // skip
}
found_binary = true;
is_execute = true;
file = "Contents/MacOS/" + pkg_name;
}
@ -1251,25 +1279,6 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
}
if (data.size() > 0) {
if (file.find("/data.mono.macos.release_debug." + architecture + "/") != -1) {
if (!p_debug) {
ret = unzGoToNextFile(src_pkg_zip);
continue; // skip
}
file = file.replace("/data.mono.macos.release_debug." + architecture + "/", "/GodotSharp/");
}
if (file.find("/data.mono.macos.release." + architecture + "/") != -1) {
if (p_debug) {
ret = unzGoToNextFile(src_pkg_zip);
continue; // skip
}
file = file.replace("/data.mono.macos.release." + architecture + "/", "/GodotSharp/");
}
if (file.ends_with(".dylib")) {
dylibs_found.push_back(file);
}
print_verbose("ADDING: " + file + " size: " + itos(data.size()));
// Write it into our application bundle.
@ -1285,7 +1294,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
if (f.is_valid()) {
f->store_buffer(data.ptr(), data.size());
f.unref();
if (is_execute) {
if (is_executable(file)) {
// chmod with 0755 if the file is executable.
FileAccess::set_unix_permissions(file, 0755);
}
@ -1324,12 +1333,35 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
return ERR_SKIP;
}
// See if we can code sign our new package.
bool sign_enabled = (p_preset->get("codesign/codesign").operator int() > 0);
bool ad_hoc = false;
int codesign_tool = p_preset->get("codesign/codesign");
switch (codesign_tool) {
case 1: { // built-in ad-hoc
ad_hoc = true;
} break;
case 2: { // "rcodesign"
ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty();
} break;
#ifdef MACOS_ENABLED
case 3: { // "codesign"
ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
} break;
#endif
default: {
};
}
String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck";
Vector<SharedObject> shared_objects;
err = save_pack(p_preset, p_debug, pack_path, &shared_objects);
// See if we can code sign our new package.
bool sign_enabled = (p_preset->get("codesign/codesign").operator int() > 0);
bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation");
if (!shared_objects.is_empty() && sign_enabled && ad_hoc && !lib_validation) {
add_message(EXPORT_MESSAGE_INFO, TTR("Entitlements Modified"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries."));
lib_validation = true;
}
String ent_path = p_preset->get("codesign/entitlements/custom_file");
String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name + "_helper.entitlements");
@ -1365,7 +1397,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
}
}
if ((bool)p_preset->get("codesign/entitlements/disable_library_validation")) {
if (lib_validation) {
ent_f->store_line("<key>com.apple.security.cs.disable-library-validation</key>");
ent_f->store_line("<true/>");
}
@ -1495,32 +1527,6 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
}
}
bool ad_hoc = false;
int codesign_tool = p_preset->get("codesign/codesign");
switch (codesign_tool) {
case 1: { // built-in ad-hoc
ad_hoc = true;
} break;
case 2: { // "rcodesign"
ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty();
} break;
#ifdef MACOS_ENABLED
case 3: { // "codesign"
ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
} break;
#endif
default: {
};
}
if (err == OK) {
bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation");
if ((!dylibs_found.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries."));
err = ERR_CANT_CREATE;
}
}
if (err == OK) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
for (int i = 0; i < shared_objects.size(); i++) {
@ -1529,8 +1535,9 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
String path_in_app = tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file();
err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, true);
} else {
String path_in_app = tmp_app_path_name.path_join(shared_objects[i].target).path_join(src_path.get_file());
err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, false);
String path_in_app = tmp_app_path_name.path_join(shared_objects[i].target);
tmp_app_dir->make_dir_recursive(path_in_app);
err = _copy_and_sign_files(da, src_path, path_in_app.path_join(src_path.get_file()), sign_enabled, p_preset, ent_path, false);
}
if (err != OK) {
break;
@ -1546,14 +1553,6 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
}
}
if (sign_enabled) {
for (int i = 0; i < dylibs_found.size(); i++) {
if (err == OK) {
err = _code_sign(p_preset, tmp_app_path_name + "/" + dylibs_found[i], ent_path, false);
}
}
}
if (err == OK && sign_enabled) {
if (ep.step(TTR("Code signing bundle"), 2)) {
return ERR_SKIP;
@ -1683,8 +1682,6 @@ void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const Stri
} else if (da->current_is_dir()) {
_zip_folder_recursive(p_zip, p_root_path, p_folder.path_join(f), p_pkg_name);
} else {
bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers") || f.ends_with(".command");
OS::DateTime dt = OS::get_singleton()->get_datetime();
zip_fileinfo zipfi;
@ -1698,7 +1695,7 @@ void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const Stri
// 0100000: regular file type
// 0000755: permissions rwxr-xr-x
// 0000644: permissions rw-r--r--
uint32_t _mode = (is_executable ? 0100755 : 0100644);
uint32_t _mode = (is_executable(dir.path_join(f)) ? 0100755 : 0100644);
zipfi.external_fa = (_mode << 16L) | !(_mode & 0200);
zipfi.internal_fa = 0;

View File

@ -97,6 +97,7 @@ class EditorExportPlatformMacOS : public EditorExportPlatform {
return true;
}
bool is_shbang(const String &p_path) const;
protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
@ -108,6 +109,7 @@ public:
virtual String get_os_name() const override { return "macOS"; }
virtual Ref<Texture2D> get_logo() const override { return logo; }
virtual bool is_executable(const String &p_path) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
List<String> list;
if (use_dmg()) {