export.cpp 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891
  1. /*************************************************************************/
  2. /* export.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
  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. #include "export.h"
  31. #include "core/io/marshalls.h"
  32. #include "core/io/resource_saver.h"
  33. #include "core/io/zip_io.h"
  34. #include "core/os/file_access.h"
  35. #include "core/os/os.h"
  36. #include "core/project_settings.h"
  37. #include "core/safe_refcount.h"
  38. #include "core/version.h"
  39. #include "editor/editor_export.h"
  40. #include "editor/editor_node.h"
  41. #include "editor/editor_settings.h"
  42. #include "main/splash.gen.h"
  43. #include "platform/iphone/logo.gen.h"
  44. #include "platform/iphone/plugin/godot_plugin_config.h"
  45. #include "string.h"
  46. #include <sys/stat.h>
  47. class EditorExportPlatformIOS : public EditorExportPlatform {
  48. GDCLASS(EditorExportPlatformIOS, EditorExportPlatform);
  49. int version_code;
  50. Ref<ImageTexture> logo;
  51. // Plugins
  52. SafeFlag plugins_changed;
  53. Thread check_for_changes_thread;
  54. SafeFlag quit_request;
  55. Mutex plugins_lock;
  56. Vector<PluginConfigIOS> plugins;
  57. typedef Error (*FileHandler)(String p_file, void *p_userdata);
  58. static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata);
  59. static Error _codesign(String p_file, void *p_userdata);
  60. struct IOSConfigData {
  61. String pkg_name;
  62. String binary_name;
  63. String plist_content;
  64. String architectures;
  65. String linker_flags;
  66. String cpp_code;
  67. String modules_buildfile;
  68. String modules_fileref;
  69. String modules_buildphase;
  70. String modules_buildgrp;
  71. Vector<String> capabilities;
  72. };
  73. struct ExportArchitecture {
  74. String name;
  75. bool is_default;
  76. ExportArchitecture() :
  77. name(""),
  78. is_default(false) {
  79. }
  80. ExportArchitecture(String p_name, bool p_is_default) {
  81. name = p_name;
  82. is_default = p_is_default;
  83. }
  84. };
  85. struct IOSExportAsset {
  86. String exported_path;
  87. bool is_framework; // framework is anything linked to the binary, otherwise it's a resource
  88. bool should_embed;
  89. };
  90. String _get_additional_plist_content();
  91. String _get_linker_flags();
  92. String _get_cpp_code();
  93. void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug);
  94. Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
  95. Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
  96. Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir);
  97. Vector<ExportArchitecture> _get_supported_architectures();
  98. Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset);
  99. void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
  100. Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
  101. Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
  102. Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
  103. Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
  104. bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
  105. String pname = p_package;
  106. if (pname.length() == 0) {
  107. if (r_error) {
  108. *r_error = TTR("Identifier is missing.");
  109. }
  110. return false;
  111. }
  112. for (int i = 0; i < pname.length(); i++) {
  113. CharType c = pname[i];
  114. if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
  115. if (r_error) {
  116. *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
  117. }
  118. return false;
  119. }
  120. }
  121. return true;
  122. }
  123. static void _check_for_changes_poll_thread(void *ud) {
  124. EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud;
  125. while (!ea->quit_request.is_set()) {
  126. // Nothing to do if we already know the plugins have changed.
  127. if (!ea->plugins_changed.is_set()) {
  128. ea->plugins_lock.lock();
  129. Vector<PluginConfigIOS> loaded_plugins = get_plugins();
  130. if (ea->plugins.size() != loaded_plugins.size()) {
  131. ea->plugins_changed.set();
  132. } else {
  133. for (int i = 0; i < ea->plugins.size(); i++) {
  134. if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) {
  135. ea->plugins_changed.set();
  136. break;
  137. }
  138. }
  139. }
  140. ea->plugins_lock.unlock();
  141. }
  142. uint64_t wait = 3000000;
  143. uint64_t time = OS::get_singleton()->get_ticks_usec();
  144. while (OS::get_singleton()->get_ticks_usec() - time < wait) {
  145. OS::get_singleton()->delay_usec(300000);
  146. if (ea->quit_request.is_set()) {
  147. break;
  148. }
  149. }
  150. }
  151. }
  152. protected:
  153. virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
  154. virtual void get_export_options(List<ExportOption> *r_options);
  155. public:
  156. virtual String get_name() const { return "iOS"; }
  157. virtual String get_os_name() const { return "iOS"; }
  158. virtual Ref<Texture> get_logo() const { return logo; }
  159. virtual bool should_update_export_options() {
  160. bool export_options_changed = plugins_changed.is_set();
  161. if (export_options_changed) {
  162. // don't clear unless we're reporting true, to avoid race
  163. plugins_changed.clear();
  164. }
  165. return export_options_changed;
  166. }
  167. virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
  168. List<String> list;
  169. list.push_back("ipa");
  170. return list;
  171. }
  172. virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
  173. virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const;
  174. virtual void get_platform_features(List<String> *r_features) {
  175. r_features->push_back("mobile");
  176. r_features->push_back("iOS");
  177. }
  178. virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
  179. }
  180. EditorExportPlatformIOS();
  181. ~EditorExportPlatformIOS();
  182. /// List the gdip files in the directory specified by the p_path parameter.
  183. static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) {
  184. Vector<String> dir_files;
  185. DirAccessRef da = DirAccess::open(p_path);
  186. if (da) {
  187. da->list_dir_begin();
  188. while (true) {
  189. String file = da->get_next();
  190. if (file.empty()) {
  191. break;
  192. }
  193. if (file == "." || file == "..") {
  194. continue;
  195. }
  196. if (da->current_is_hidden()) {
  197. continue;
  198. }
  199. if (da->current_is_dir()) {
  200. if (p_check_directories) {
  201. Vector<String> directory_files = list_plugin_config_files(p_path.plus_file(file), false);
  202. for (int i = 0; i < directory_files.size(); ++i) {
  203. dir_files.push_back(file.plus_file(directory_files[i]));
  204. }
  205. }
  206. continue;
  207. }
  208. if (file.ends_with(PluginConfigIOS::PLUGIN_CONFIG_EXT)) {
  209. dir_files.push_back(file);
  210. }
  211. }
  212. da->list_dir_end();
  213. }
  214. return dir_files;
  215. }
  216. static Vector<PluginConfigIOS> get_plugins() {
  217. Vector<PluginConfigIOS> loaded_plugins;
  218. String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins");
  219. if (DirAccess::exists(plugins_dir)) {
  220. Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true);
  221. if (!plugins_filenames.empty()) {
  222. Ref<ConfigFile> config_file = memnew(ConfigFile);
  223. for (int i = 0; i < plugins_filenames.size(); i++) {
  224. PluginConfigIOS config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
  225. if (config.valid_config) {
  226. loaded_plugins.push_back(config);
  227. } else {
  228. print_error("Invalid plugin config file " + plugins_filenames[i]);
  229. }
  230. }
  231. }
  232. }
  233. return loaded_plugins;
  234. }
  235. static Vector<PluginConfigIOS> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
  236. Vector<PluginConfigIOS> enabled_plugins;
  237. Vector<PluginConfigIOS> all_plugins = get_plugins();
  238. for (int i = 0; i < all_plugins.size(); i++) {
  239. PluginConfigIOS plugin = all_plugins[i];
  240. bool enabled = p_presets->get("plugins/" + plugin.name);
  241. if (enabled) {
  242. enabled_plugins.push_back(plugin);
  243. }
  244. }
  245. return enabled_plugins;
  246. }
  247. };
  248. void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
  249. String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name");
  250. r_features->push_back("pvrtc");
  251. if (driver == "GLES3") {
  252. r_features->push_back("etc2");
  253. }
  254. Vector<String> architectures = _get_preset_architectures(p_preset);
  255. for (int i = 0; i < architectures.size(); ++i) {
  256. r_features->push_back(architectures[i]);
  257. }
  258. }
  259. Vector<EditorExportPlatformIOS::ExportArchitecture> EditorExportPlatformIOS::_get_supported_architectures() {
  260. Vector<ExportArchitecture> archs;
  261. archs.push_back(ExportArchitecture("armv7", false)); // Disabled by default, not included in official templates.
  262. archs.push_back(ExportArchitecture("arm64", true));
  263. return archs;
  264. }
  265. struct LoadingScreenInfo {
  266. const char *preset_key;
  267. const char *export_name;
  268. };
  269. static const LoadingScreenInfo loading_screen_infos[] = {
  270. { "landscape_launch_screens/iphone_2436x1125", "Default-Landscape-X.png" },
  271. { "landscape_launch_screens/iphone_2208x1242", "[email protected]" },
  272. { "landscape_launch_screens/ipad_1024x768", "Default-Landscape.png" },
  273. { "landscape_launch_screens/ipad_2048x1536", "[email protected]" },
  274. { "portrait_launch_screens/iphone_640x960", "[email protected]" },
  275. { "portrait_launch_screens/iphone_640x1136", "[email protected]" },
  276. { "portrait_launch_screens/iphone_750x1334", "[email protected]" },
  277. { "portrait_launch_screens/iphone_1125x2436", "Default-Portrait-X.png" },
  278. { "portrait_launch_screens/ipad_768x1024", "Default-Portrait.png" },
  279. { "portrait_launch_screens/ipad_1536x2048", "[email protected]" },
  280. { "portrait_launch_screens/iphone_1242x2208", "[email protected]" }
  281. };
  282. void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) {
  283. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
  284. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
  285. Vector<ExportArchitecture> architectures = _get_supported_architectures();
  286. for (int i = 0; i < architectures.size(); ++i) {
  287. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + architectures[i].name), architectures[i].is_default));
  288. }
  289. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), ""));
  290. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), ""));
  291. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "iPhone Developer"));
  292. r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1));
  293. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release"), ""));
  294. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), ""));
  295. r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0));
  296. r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2));
  297. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
  298. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine"));
  299. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
  300. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
  301. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0"));
  302. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0"));
  303. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
  304. Vector<PluginConfigIOS> found_plugins = get_plugins();
  305. for (int i = 0; i < found_plugins.size(); i++) {
  306. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false));
  307. }
  308. plugins_changed.clear();
  309. plugins = found_plugins;
  310. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false));
  311. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false));
  312. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false));
  313. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false));
  314. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), ""));
  315. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), ""));
  316. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), ""));
  317. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with retina display
  318. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad
  319. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store
  320. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with retina HD display
  321. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with retina display
  322. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro
  323. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight
  324. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with retina display
  325. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false));
  326. r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale To Fit,Scale To Fill,Scale"), 0));
  327. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png"), ""));
  328. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png"), ""));
  329. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false));
  330. r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color()));
  331. for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) {
  332. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png"), ""));
  333. }
  334. }
  335. void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug) {
  336. static const String export_method_string[] = {
  337. "app-store",
  338. "development",
  339. "ad-hoc",
  340. "enterprise"
  341. };
  342. static const String storyboard_image_scale_mode[] = {
  343. "center",
  344. "scaleAspectFit",
  345. "scaleAspectFill",
  346. "scaleToFill"
  347. };
  348. String str;
  349. String strnew;
  350. str.parse_utf8((const char *)pfile.ptr(), pfile.size());
  351. Vector<String> lines = str.split("\n");
  352. for (int i = 0; i < lines.size(); i++) {
  353. if (lines[i].find("$binary") != -1) {
  354. strnew += lines[i].replace("$binary", p_config.binary_name) + "\n";
  355. } else if (lines[i].find("$modules_buildfile") != -1) {
  356. strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n";
  357. } else if (lines[i].find("$modules_fileref") != -1) {
  358. strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n";
  359. } else if (lines[i].find("$modules_buildphase") != -1) {
  360. strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n";
  361. } else if (lines[i].find("$modules_buildgrp") != -1) {
  362. strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n";
  363. } else if (lines[i].find("$name") != -1) {
  364. strnew += lines[i].replace("$name", p_config.pkg_name) + "\n";
  365. } else if (lines[i].find("$info") != -1) {
  366. strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n";
  367. } else if (lines[i].find("$identifier") != -1) {
  368. strnew += lines[i].replace("$identifier", p_preset->get("application/identifier")) + "\n";
  369. } else if (lines[i].find("$short_version") != -1) {
  370. strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n";
  371. } else if (lines[i].find("$version") != -1) {
  372. strnew += lines[i].replace("$version", p_preset->get("application/version")) + "\n";
  373. } else if (lines[i].find("$signature") != -1) {
  374. strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n";
  375. } else if (lines[i].find("$copyright") != -1) {
  376. strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n";
  377. } else if (lines[i].find("$team_id") != -1) {
  378. strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n";
  379. } else if (lines[i].find("$default_build_config") != -1) {
  380. strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n";
  381. } else if (lines[i].find("$export_method") != -1) {
  382. int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release");
  383. strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n";
  384. } else if (lines[i].find("$provisioning_profile_uuid_release") != -1) {
  385. strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get("application/provisioning_profile_uuid_release")) + "\n";
  386. } else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) {
  387. strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get("application/provisioning_profile_uuid_debug")) + "\n";
  388. } else if (lines[i].find("$provisioning_profile_uuid") != -1) {
  389. String uuid = p_debug ? p_preset->get("application/provisioning_profile_uuid_debug") : p_preset->get("application/provisioning_profile_uuid_release");
  390. strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n";
  391. } else if (lines[i].find("$code_sign_identity_debug") != -1) {
  392. strnew += lines[i].replace("$code_sign_identity_debug", p_preset->get("application/code_sign_identity_debug")) + "\n";
  393. } else if (lines[i].find("$code_sign_identity_release") != -1) {
  394. strnew += lines[i].replace("$code_sign_identity_release", p_preset->get("application/code_sign_identity_release")) + "\n";
  395. } else if (lines[i].find("$additional_plist_content") != -1) {
  396. strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n";
  397. } else if (lines[i].find("$godot_archs") != -1) {
  398. strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n";
  399. } else if (lines[i].find("$linker_flags") != -1) {
  400. strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n";
  401. } else if (lines[i].find("$targeted_device_family") != -1) {
  402. String xcode_value;
  403. switch ((int)p_preset->get("application/targeted_device_family")) {
  404. case 0: // iPhone
  405. xcode_value = "1";
  406. break;
  407. case 1: // iPad
  408. xcode_value = "2";
  409. break;
  410. case 2: // iPhone & iPad
  411. xcode_value = "1,2";
  412. break;
  413. }
  414. strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n";
  415. } else if (lines[i].find("$cpp_code") != -1) {
  416. strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n";
  417. } else if (lines[i].find("$docs_in_place") != -1) {
  418. strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "<true/>" : "<false/>") + "\n";
  419. } else if (lines[i].find("$docs_sharing") != -1) {
  420. strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "<true/>" : "<false/>") + "\n";
  421. } else if (lines[i].find("$entitlements_push_notifications") != -1) {
  422. bool is_on = p_preset->get("capabilities/push_notifications");
  423. strnew += lines[i].replace("$entitlements_push_notifications", is_on ? "<key>aps-environment</key><string>development</string>" : "") + "\n";
  424. } else if (lines[i].find("$required_device_capabilities") != -1) {
  425. String capabilities;
  426. // I've removed armv7 as we can run on 64bit only devices
  427. // Note that capabilities listed here are requirements for the app to be installed.
  428. // They don't enable anything.
  429. Vector<String> capabilities_list = p_config.capabilities;
  430. if ((bool)p_preset->get("capabilities/access_wifi") && capabilities_list.find("wifi") != -1) {
  431. capabilities_list.push_back("wifi");
  432. }
  433. for (int idx = 0; idx < capabilities_list.size(); idx++) {
  434. capabilities += "<string>" + capabilities_list[idx] + "</string>\n";
  435. }
  436. strnew += lines[i].replace("$required_device_capabilities", capabilities);
  437. } else if (lines[i].find("$interface_orientations") != -1) {
  438. String orientations;
  439. const OS::ScreenOrientation screen_orientation =
  440. OS::get_singleton()->get_screen_orientation_from_string(GLOBAL_GET("display/window/handheld/orientation"));
  441. switch (screen_orientation) {
  442. case OS::SCREEN_LANDSCAPE:
  443. orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
  444. break;
  445. case OS::SCREEN_PORTRAIT:
  446. orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
  447. break;
  448. case OS::SCREEN_REVERSE_LANDSCAPE:
  449. orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
  450. break;
  451. case OS::SCREEN_REVERSE_PORTRAIT:
  452. orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
  453. break;
  454. case OS::SCREEN_SENSOR_LANDSCAPE:
  455. // Allow both landscape orientations depending on sensor direction.
  456. orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
  457. orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
  458. break;
  459. case OS::SCREEN_SENSOR_PORTRAIT:
  460. // Allow both portrait orientations depending on sensor direction.
  461. orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
  462. orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
  463. break;
  464. case OS::SCREEN_SENSOR:
  465. // Allow all screen orientations depending on sensor direction.
  466. orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
  467. orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
  468. orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
  469. orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
  470. break;
  471. }
  472. strnew += lines[i].replace("$interface_orientations", orientations);
  473. } else if (lines[i].find("$camera_usage_description") != -1) {
  474. String description = p_preset->get("privacy/camera_usage_description");
  475. strnew += lines[i].replace("$camera_usage_description", description) + "\n";
  476. } else if (lines[i].find("$microphone_usage_description") != -1) {
  477. String description = p_preset->get("privacy/microphone_usage_description");
  478. strnew += lines[i].replace("$microphone_usage_description", description) + "\n";
  479. } else if (lines[i].find("$photolibrary_usage_description") != -1) {
  480. String description = p_preset->get("privacy/photolibrary_usage_description");
  481. strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n";
  482. } else if (lines[i].find("$plist_launch_screen_name") != -1) {
  483. bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
  484. String value = is_on ? "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>" : "";
  485. strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n";
  486. } else if (lines[i].find("$pbx_launch_screen_file_reference") != -1) {
  487. bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
  488. String value = is_on ? "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };" : "";
  489. strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n";
  490. } else if (lines[i].find("$pbx_launch_screen_copy_files") != -1) {
  491. bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
  492. String value = is_on ? "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */," : "";
  493. strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n";
  494. } else if (lines[i].find("$pbx_launch_screen_build_phase") != -1) {
  495. bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
  496. String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */," : "";
  497. strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n";
  498. } else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) {
  499. bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
  500. String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };" : "";
  501. strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n";
  502. } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) {
  503. bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
  504. String value = is_on ? "" : "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;";
  505. strnew += lines[i].replace("$pbx_launch_image_usage_setting", value) + "\n";
  506. } else if (lines[i].find("$launch_screen_image_mode") != -1) {
  507. int image_scale_mode = p_preset->get("storyboard/image_scale_mode");
  508. String value;
  509. switch (image_scale_mode) {
  510. case 0: {
  511. String logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
  512. bool is_on = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize");
  513. // If custom logo is not specified, Godot does not scale default one, so we should do the same.
  514. value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center";
  515. } break;
  516. default: {
  517. value = storyboard_image_scale_mode[image_scale_mode - 1];
  518. }
  519. }
  520. strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n";
  521. } else if (lines[i].find("$launch_screen_background_color") != -1) {
  522. bool use_custom = p_preset->get("storyboard/use_custom_bg_color");
  523. Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : ProjectSettings::get_singleton()->get("application/boot_splash/bg_color");
  524. const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\"";
  525. Dictionary value_dictionary;
  526. value_dictionary["red"] = color.r;
  527. value_dictionary["green"] = color.g;
  528. value_dictionary["blue"] = color.b;
  529. value_dictionary["alpha"] = color.a;
  530. String value = value_format.format(value_dictionary, "$_");
  531. strnew += lines[i].replace("$launch_screen_background_color", value) + "\n";
  532. } else {
  533. strnew += lines[i] + "\n";
  534. }
  535. }
  536. // !BAS! I'm assuming the 9 in the original code was a typo. I've added -1 or else it seems to also be adding our terminating zero...
  537. // should apply the same fix in our OSX export.
  538. CharString cs = strnew.utf8();
  539. pfile.resize(cs.size() - 1);
  540. for (int i = 0; i < cs.size() - 1; i++) {
  541. pfile.write[i] = cs[i];
  542. }
  543. }
  544. String EditorExportPlatformIOS::_get_additional_plist_content() {
  545. Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
  546. String result;
  547. for (int i = 0; i < export_plugins.size(); ++i) {
  548. result += export_plugins[i]->get_ios_plist_content();
  549. }
  550. return result;
  551. }
  552. String EditorExportPlatformIOS::_get_linker_flags() {
  553. Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
  554. String result;
  555. for (int i = 0; i < export_plugins.size(); ++i) {
  556. String flags = export_plugins[i]->get_ios_linker_flags();
  557. if (flags.length() == 0) {
  558. continue;
  559. }
  560. if (result.length() > 0) {
  561. result += ' ';
  562. }
  563. result += flags;
  564. }
  565. // the flags will be enclosed in quotes, so need to escape them
  566. return result.replace("\"", "\\\"");
  567. }
  568. String EditorExportPlatformIOS::_get_cpp_code() {
  569. Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
  570. String result;
  571. for (int i = 0; i < export_plugins.size(); ++i) {
  572. result += export_plugins[i]->get_ios_cpp_code();
  573. }
  574. return result;
  575. }
  576. struct IconInfo {
  577. const char *preset_key;
  578. const char *idiom;
  579. const char *export_name;
  580. const char *actual_size_side;
  581. const char *scale;
  582. const char *unscaled_size;
  583. bool is_required;
  584. };
  585. static const IconInfo icon_infos[] = {
  586. { "required_icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60", true },
  587. { "required_icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40", true },
  588. { "required_icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76", false },
  589. { "required_icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", false },
  590. { "optional_icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60", false },
  591. { "optional_icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76", false },
  592. { "optional_icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5", false },
  593. { "optional_icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40", false },
  594. { "optional_icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40", false },
  595. { "optional_icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40", false }
  596. };
  597. Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) {
  598. String json_description = "{\"images\":[";
  599. String sizes;
  600. DirAccess *da = DirAccess::open(p_iconset_dir);
  601. ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'.");
  602. for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) {
  603. IconInfo info = icon_infos[i];
  604. String icon_path = p_preset->get(info.preset_key);
  605. if (icon_path.length() == 0) {
  606. if (info.is_required) {
  607. ERR_PRINT("Required icon is not specified in the preset");
  608. return ERR_UNCONFIGURED;
  609. }
  610. continue;
  611. }
  612. Error err = da->copy(icon_path, p_iconset_dir + info.export_name);
  613. if (err) {
  614. memdelete(da);
  615. String err_str = String("Failed to export icon: ") + icon_path;
  616. ERR_PRINT(err_str.utf8().get_data());
  617. return err;
  618. }
  619. sizes += String(info.actual_size_side) + "\n";
  620. if (i > 0) {
  621. json_description += ",";
  622. }
  623. json_description += String("{");
  624. json_description += String("\"idiom\":") + "\"" + info.idiom + "\",";
  625. json_description += String("\"size\":") + "\"" + info.unscaled_size + "\",";
  626. json_description += String("\"scale\":") + "\"" + info.scale + "\",";
  627. json_description += String("\"filename\":") + "\"" + info.export_name + "\"";
  628. json_description += String("}");
  629. }
  630. json_description += "]}";
  631. memdelete(da);
  632. FileAccess *json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE);
  633. ERR_FAIL_COND_V(!json_file, ERR_CANT_CREATE);
  634. CharString json_utf8 = json_description.utf8();
  635. json_file->store_buffer((const uint8_t *)json_utf8.get_data(), json_utf8.length());
  636. memdelete(json_file);
  637. FileAccess *sizes_file = FileAccess::open(p_iconset_dir + "sizes", FileAccess::WRITE);
  638. ERR_FAIL_COND_V(!sizes_file, ERR_CANT_CREATE);
  639. CharString sizes_utf8 = sizes.utf8();
  640. sizes_file->store_buffer((const uint8_t *)sizes_utf8.get_data(), sizes_utf8.length());
  641. memdelete(sizes_file);
  642. return OK;
  643. }
  644. Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
  645. const String custom_launch_image_2x = p_preset->get("storyboard/custom_image@2x");
  646. const String custom_launch_image_3x = p_preset->get("storyboard/custom_image@3x");
  647. if (custom_launch_image_2x.length() > 0 && custom_launch_image_3x.length() > 0) {
  648. Ref<Image> image;
  649. String image_path = p_dest_dir.plus_file("[email protected]");
  650. image.instance();
  651. Error err = image->load(custom_launch_image_2x);
  652. if (err) {
  653. image.unref();
  654. return err;
  655. }
  656. if (image->save_png(image_path) != OK) {
  657. return ERR_FILE_CANT_WRITE;
  658. }
  659. image.unref();
  660. image_path = p_dest_dir.plus_file("[email protected]");
  661. image.instance();
  662. err = image->load(custom_launch_image_3x);
  663. if (err) {
  664. image.unref();
  665. return err;
  666. }
  667. if (image->save_png(image_path) != OK) {
  668. return ERR_FILE_CANT_WRITE;
  669. }
  670. } else {
  671. Ref<Image> splash;
  672. const String splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
  673. if (!splash_path.empty()) {
  674. splash.instance();
  675. const Error err = splash->load(splash_path);
  676. if (err) {
  677. splash.unref();
  678. }
  679. }
  680. if (splash.is_null()) {
  681. splash = Ref<Image>(memnew(Image(boot_splash_png)));
  682. }
  683. // Using same image for both @2x and @3x
  684. // because Godot's own boot logo uses single image for all resolutions.
  685. // Also not using @1x image, because devices using this image variant
  686. // are not supported by iOS 9, which is minimal target.
  687. const String splash_png_path_2x = p_dest_dir.plus_file("[email protected]");
  688. const String splash_png_path_3x = p_dest_dir.plus_file("[email protected]");
  689. if (splash->save_png(splash_png_path_2x) != OK) {
  690. return ERR_FILE_CANT_WRITE;
  691. }
  692. if (splash->save_png(splash_png_path_3x) != OK) {
  693. return ERR_FILE_CANT_WRITE;
  694. }
  695. }
  696. return OK;
  697. }
  698. Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
  699. DirAccess *da = DirAccess::open(p_dest_dir);
  700. ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'.");
  701. for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) {
  702. LoadingScreenInfo info = loading_screen_infos[i];
  703. String loading_screen_file = p_preset->get(info.preset_key);
  704. if (loading_screen_file.size() > 0) {
  705. Error err = da->copy(loading_screen_file, p_dest_dir + info.export_name);
  706. if (err) {
  707. memdelete(da);
  708. String err_str = String("Failed to export loading screen (") + info.preset_key + ") from path '" + loading_screen_file + "'.";
  709. ERR_PRINT(err_str.utf8().get_data());
  710. return err;
  711. }
  712. }
  713. }
  714. memdelete(da);
  715. return OK;
  716. }
  717. Error EditorExportPlatformIOS::_walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata) {
  718. Vector<String> dirs;
  719. String path;
  720. String current_dir = p_da->get_current_dir();
  721. p_da->list_dir_begin();
  722. while ((path = p_da->get_next()).length() != 0) {
  723. if (p_da->current_is_dir()) {
  724. if (path != "." && path != "..") {
  725. dirs.push_back(path);
  726. }
  727. } else {
  728. Error err = p_handler(current_dir.plus_file(path), p_userdata);
  729. if (err) {
  730. p_da->list_dir_end();
  731. return err;
  732. }
  733. }
  734. }
  735. p_da->list_dir_end();
  736. for (int i = 0; i < dirs.size(); ++i) {
  737. String dir = dirs[i];
  738. p_da->change_dir(dir);
  739. Error err = _walk_dir_recursive(p_da, p_handler, p_userdata);
  740. p_da->change_dir("..");
  741. if (err) {
  742. return err;
  743. }
  744. }
  745. return OK;
  746. }
  747. struct CodesignData {
  748. const Ref<EditorExportPreset> &preset;
  749. bool debug;
  750. CodesignData(const Ref<EditorExportPreset> &p_preset, bool p_debug) :
  751. preset(p_preset),
  752. debug(p_debug) {
  753. }
  754. };
  755. Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) {
  756. if (p_file.ends_with(".dylib")) {
  757. CodesignData *data = (CodesignData *)p_userdata;
  758. print_line(String("Signing ") + p_file);
  759. List<String> codesign_args;
  760. codesign_args.push_back("-f");
  761. codesign_args.push_back("-s");
  762. codesign_args.push_back(data->preset->get(data->debug ? "application/code_sign_identity_debug" : "application/code_sign_identity_release"));
  763. codesign_args.push_back(p_file);
  764. return OS::get_singleton()->execute("codesign", codesign_args, true);
  765. }
  766. return OK;
  767. }
  768. struct PbxId {
  769. private:
  770. static char _hex_char(uint8_t four_bits) {
  771. if (four_bits < 10) {
  772. return ('0' + four_bits);
  773. }
  774. return 'A' + (four_bits - 10);
  775. }
  776. static String _hex_pad(uint32_t num) {
  777. Vector<char> ret;
  778. ret.resize(sizeof(num) * 2);
  779. for (uint64_t i = 0; i < sizeof(num) * 2; ++i) {
  780. uint8_t four_bits = (num >> (sizeof(num) * 8 - (i + 1) * 4)) & 0xF;
  781. ret.write[i] = _hex_char(four_bits);
  782. }
  783. return String::utf8(ret.ptr(), ret.size());
  784. }
  785. public:
  786. uint32_t high_bits;
  787. uint32_t mid_bits;
  788. uint32_t low_bits;
  789. String str() const {
  790. return _hex_pad(high_bits) + _hex_pad(mid_bits) + _hex_pad(low_bits);
  791. }
  792. PbxId &operator++() {
  793. low_bits++;
  794. if (!low_bits) {
  795. mid_bits++;
  796. if (!mid_bits) {
  797. high_bits++;
  798. }
  799. }
  800. return *this;
  801. }
  802. };
  803. struct ExportLibsData {
  804. Vector<String> lib_paths;
  805. String dest_dir;
  806. };
  807. void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) {
  808. // that is just a random number, we just need Godot IDs not to clash with
  809. // existing IDs in the project.
  810. PbxId current_id = { 0x58938401, 0, 0 };
  811. String pbx_files;
  812. String pbx_frameworks_build;
  813. String pbx_frameworks_refs;
  814. String pbx_resources_build;
  815. String pbx_resources_refs;
  816. String pbx_embeded_frameworks;
  817. const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") +
  818. "$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"<group>\"; };\n";
  819. for (int i = 0; i < p_additional_assets.size(); ++i) {
  820. String additional_asset_info_format = file_info_format;
  821. String build_id = (++current_id).str();
  822. String ref_id = (++current_id).str();
  823. String framework_id = "";
  824. const IOSExportAsset &asset = p_additional_assets[i];
  825. String type;
  826. if (asset.exported_path.ends_with(".framework")) {
  827. if (asset.should_embed) {
  828. additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
  829. framework_id = (++current_id).str();
  830. pbx_embeded_frameworks += framework_id + ",\n";
  831. }
  832. type = "wrapper.framework";
  833. } else if (asset.exported_path.ends_with(".xcframework")) {
  834. if (asset.should_embed) {
  835. additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
  836. framework_id = (++current_id).str();
  837. pbx_embeded_frameworks += framework_id + ",\n";
  838. }
  839. type = "wrapper.xcframework";
  840. } else if (asset.exported_path.ends_with(".dylib")) {
  841. type = "compiled.mach-o.dylib";
  842. } else if (asset.exported_path.ends_with(".a")) {
  843. type = "archive.ar";
  844. } else {
  845. type = "file";
  846. }
  847. String &pbx_build = asset.is_framework ? pbx_frameworks_build : pbx_resources_build;
  848. String &pbx_refs = asset.is_framework ? pbx_frameworks_refs : pbx_resources_refs;
  849. if (pbx_build.length() > 0) {
  850. pbx_build += ",\n";
  851. pbx_refs += ",\n";
  852. }
  853. pbx_build += build_id;
  854. pbx_refs += ref_id;
  855. Dictionary format_dict;
  856. format_dict["build_id"] = build_id;
  857. format_dict["ref_id"] = ref_id;
  858. format_dict["name"] = asset.exported_path.get_file();
  859. format_dict["file_path"] = asset.exported_path;
  860. format_dict["file_type"] = type;
  861. if (framework_id.length() > 0) {
  862. format_dict["framework_id"] = framework_id;
  863. }
  864. pbx_files += additional_asset_info_format.format(format_dict, "$_");
  865. }
  866. // Note, frameworks like gamekit are always included in our project.pbxprof file
  867. // even if turned off in capabilities.
  868. String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size());
  869. str = str.replace("$additional_pbx_files", pbx_files);
  870. str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build);
  871. str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs);
  872. str = str.replace("$additional_pbx_resources_build", pbx_resources_build);
  873. str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs);
  874. str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks);
  875. CharString cs = str.utf8();
  876. p_project_data.resize(cs.size() - 1);
  877. for (int i = 0; i < cs.size() - 1; i++) {
  878. p_project_data.write[i] = cs[i];
  879. }
  880. }
  881. Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
  882. DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  883. ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
  884. String binary_name = p_out_dir.get_file().get_basename();
  885. DirAccess *da = DirAccess::create_for_path(p_asset);
  886. if (!da) {
  887. memdelete(filesystem_da);
  888. ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + ".");
  889. }
  890. bool file_exists = da->file_exists(p_asset);
  891. bool dir_exists = da->dir_exists(p_asset);
  892. if (!file_exists && !dir_exists) {
  893. memdelete(da);
  894. memdelete(filesystem_da);
  895. return ERR_FILE_NOT_FOUND;
  896. }
  897. String base_dir = p_asset.get_base_dir().replace("res://", "");
  898. String destination_dir;
  899. String destination;
  900. String asset_path;
  901. bool create_framework = false;
  902. if (p_is_framework && p_asset.ends_with(".dylib")) {
  903. // For iOS we need to turn .dylib into .framework
  904. // to be able to send application to AppStore
  905. asset_path = String("dylibs").plus_file(base_dir);
  906. String file_name;
  907. if (!p_custom_file_name) {
  908. file_name = p_asset.get_basename().get_file();
  909. } else {
  910. file_name = *p_custom_file_name;
  911. }
  912. String framework_name = file_name + ".framework";
  913. asset_path = asset_path.plus_file(framework_name);
  914. destination_dir = p_out_dir.plus_file(asset_path);
  915. destination = destination_dir.plus_file(file_name);
  916. create_framework = true;
  917. } else if (p_is_framework && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) {
  918. asset_path = String("dylibs").plus_file(base_dir);
  919. String file_name;
  920. if (!p_custom_file_name) {
  921. file_name = p_asset.get_file();
  922. } else {
  923. file_name = *p_custom_file_name;
  924. }
  925. asset_path = asset_path.plus_file(file_name);
  926. destination_dir = p_out_dir.plus_file(asset_path);
  927. destination = destination_dir;
  928. } else {
  929. asset_path = base_dir;
  930. String file_name;
  931. if (!p_custom_file_name) {
  932. file_name = p_asset.get_file();
  933. } else {
  934. file_name = *p_custom_file_name;
  935. }
  936. destination_dir = p_out_dir.plus_file(asset_path);
  937. asset_path = asset_path.plus_file(file_name);
  938. destination = p_out_dir.plus_file(asset_path);
  939. }
  940. if (!filesystem_da->dir_exists(destination_dir)) {
  941. Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
  942. if (make_dir_err) {
  943. memdelete(da);
  944. memdelete(filesystem_da);
  945. return make_dir_err;
  946. }
  947. }
  948. Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
  949. memdelete(da);
  950. if (err) {
  951. memdelete(filesystem_da);
  952. return err;
  953. }
  954. IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed };
  955. r_exported_assets.push_back(exported_asset);
  956. if (create_framework) {
  957. String file_name;
  958. if (!p_custom_file_name) {
  959. file_name = p_asset.get_basename().get_file();
  960. } else {
  961. file_name = *p_custom_file_name;
  962. }
  963. String framework_name = file_name + ".framework";
  964. // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
  965. {
  966. List<String> install_name_args;
  967. install_name_args.push_back("-id");
  968. install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name));
  969. install_name_args.push_back(destination);
  970. OS::get_singleton()->execute("install_name_tool", install_name_args, true);
  971. }
  972. // Creating Info.plist
  973. {
  974. String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
  975. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
  976. "<plist version=\"1.0\">\n"
  977. "<dict>\n"
  978. "<key>CFBundleShortVersionString</key>\n"
  979. "<string>1.0</string>\n"
  980. "<key>CFBundleIdentifier</key>\n"
  981. "<string>com.gdnative.framework.$name</string>\n"
  982. "<key>CFBundleName</key>\n"
  983. "<string>$name</string>\n"
  984. "<key>CFBundleExecutable</key>\n"
  985. "<string>$name</string>\n"
  986. "<key>DTPlatformName</key>\n"
  987. "<string>iphoneos</string>\n"
  988. "<key>CFBundleInfoDictionaryVersion</key>\n"
  989. "<string>6.0</string>\n"
  990. "<key>CFBundleVersion</key>\n"
  991. "<string>1</string>\n"
  992. "<key>CFBundlePackageType</key>\n"
  993. "<string>FMWK</string>\n"
  994. "<key>MinimumOSVersion</key>\n"
  995. "<string>10.0</string>\n"
  996. "</dict>\n"
  997. "</plist>";
  998. String info_plist = info_plist_format.replace("$name", file_name);
  999. FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE);
  1000. if (f) {
  1001. f->store_string(info_plist);
  1002. f->close();
  1003. memdelete(f);
  1004. }
  1005. }
  1006. }
  1007. memdelete(filesystem_da);
  1008. return OK;
  1009. }
  1010. Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
  1011. for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) {
  1012. String asset = p_assets[f_idx];
  1013. if (!asset.begins_with("res://")) {
  1014. // either SDK-builtin or already a part of the export template
  1015. IOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed };
  1016. r_exported_assets.push_back(exported_asset);
  1017. } else {
  1018. Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
  1019. ERR_FAIL_COND_V(err, err);
  1020. }
  1021. }
  1022. return OK;
  1023. }
  1024. Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) {
  1025. Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
  1026. for (int i = 0; i < export_plugins.size(); i++) {
  1027. Vector<String> linked_frameworks = export_plugins[i]->get_ios_frameworks();
  1028. Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets);
  1029. ERR_FAIL_COND_V(err, err);
  1030. Vector<String> embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks();
  1031. err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets);
  1032. ERR_FAIL_COND_V(err, err);
  1033. Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();
  1034. for (int j = 0; j < project_static_libs.size(); j++) {
  1035. project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project
  1036. }
  1037. err = _export_additional_assets(p_out_dir, project_static_libs, true, true, r_exported_assets);
  1038. ERR_FAIL_COND_V(err, err);
  1039. Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files();
  1040. err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets);
  1041. ERR_FAIL_COND_V(err, err);
  1042. }
  1043. Vector<String> library_paths;
  1044. for (int i = 0; i < p_libraries.size(); ++i) {
  1045. library_paths.push_back(p_libraries[i].path);
  1046. }
  1047. Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets);
  1048. ERR_FAIL_COND_V(err, err);
  1049. return OK;
  1050. }
  1051. Vector<String> EditorExportPlatformIOS::_get_preset_architectures(const Ref<EditorExportPreset> &p_preset) {
  1052. Vector<ExportArchitecture> all_archs = _get_supported_architectures();
  1053. Vector<String> enabled_archs;
  1054. for (int i = 0; i < all_archs.size(); ++i) {
  1055. bool is_enabled = p_preset->get("architectures/" + all_archs[i].name);
  1056. if (is_enabled) {
  1057. enabled_archs.push_back(all_archs[i].name);
  1058. }
  1059. }
  1060. return enabled_archs;
  1061. }
  1062. Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug) {
  1063. String plugin_definition_cpp_code;
  1064. String plugin_initialization_cpp_code;
  1065. String plugin_deinitialization_cpp_code;
  1066. Vector<String> plugin_linked_dependencies;
  1067. Vector<String> plugin_embedded_dependencies;
  1068. Vector<String> plugin_files;
  1069. Vector<PluginConfigIOS> enabled_plugins = get_enabled_plugins(p_preset);
  1070. Vector<String> added_linked_dependenciy_names;
  1071. Vector<String> added_embedded_dependenciy_names;
  1072. HashMap<String, String> plist_values;
  1073. Set<String> plugin_linker_flags;
  1074. Error err;
  1075. for (int i = 0; i < enabled_plugins.size(); i++) {
  1076. PluginConfigIOS plugin = enabled_plugins[i];
  1077. // Export plugin binary.
  1078. String plugin_main_binary = get_plugin_main_binary(plugin, p_debug);
  1079. String plugin_binary_result_file = plugin.binary.get_file();
  1080. // We shouldn't embed .xcframework that contains static libraries.
  1081. // Static libraries are not embedded anyway.
  1082. err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets);
  1083. ERR_FAIL_COND_V(err, err);
  1084. // Adding dependencies.
  1085. // Use separate container for names to check for duplicates.
  1086. for (int j = 0; j < plugin.linked_dependencies.size(); j++) {
  1087. String dependency = plugin.linked_dependencies[j];
  1088. String name = dependency.get_file();
  1089. if (added_linked_dependenciy_names.find(name) != -1) {
  1090. continue;
  1091. }
  1092. added_linked_dependenciy_names.push_back(name);
  1093. plugin_linked_dependencies.push_back(dependency);
  1094. }
  1095. for (int j = 0; j < plugin.system_dependencies.size(); j++) {
  1096. String dependency = plugin.system_dependencies[j];
  1097. String name = dependency.get_file();
  1098. if (added_linked_dependenciy_names.find(name) != -1) {
  1099. continue;
  1100. }
  1101. added_linked_dependenciy_names.push_back(name);
  1102. plugin_linked_dependencies.push_back(dependency);
  1103. }
  1104. for (int j = 0; j < plugin.embedded_dependencies.size(); j++) {
  1105. String dependency = plugin.embedded_dependencies[j];
  1106. String name = dependency.get_file();
  1107. if (added_embedded_dependenciy_names.find(name) != -1) {
  1108. continue;
  1109. }
  1110. added_embedded_dependenciy_names.push_back(name);
  1111. plugin_embedded_dependencies.push_back(dependency);
  1112. }
  1113. plugin_files.append_array(plugin.files_to_copy);
  1114. // Capabilities
  1115. // Also checking for duplicates.
  1116. for (int j = 0; j < plugin.capabilities.size(); j++) {
  1117. String capability = plugin.capabilities[j];
  1118. if (p_config_data.capabilities.find(capability) != -1) {
  1119. continue;
  1120. }
  1121. p_config_data.capabilities.push_back(capability);
  1122. }
  1123. // Linker flags
  1124. // Checking duplicates
  1125. for (int j = 0; j < plugin.linker_flags.size(); j++) {
  1126. String linker_flag = plugin.linker_flags[j];
  1127. plugin_linker_flags.insert(linker_flag);
  1128. }
  1129. // Plist
  1130. // Using hash map container to remove duplicates
  1131. const String *K = nullptr;
  1132. while ((K = plugin.plist.next(K))) {
  1133. String key = *K;
  1134. String value = plugin.plist[key];
  1135. if (key.empty() || value.empty()) {
  1136. continue;
  1137. }
  1138. plist_values[key] = value;
  1139. }
  1140. // CPP Code
  1141. String definition_comment = "// Plugin: " + plugin.name + "\n";
  1142. String initialization_method = plugin.initialization_method + "();\n";
  1143. String deinitialization_method = plugin.deinitialization_method + "();\n";
  1144. plugin_definition_cpp_code += definition_comment +
  1145. "extern void " + initialization_method +
  1146. "extern void " + deinitialization_method + "\n";
  1147. plugin_initialization_cpp_code += "\t" + initialization_method;
  1148. plugin_deinitialization_cpp_code += "\t" + deinitialization_method;
  1149. }
  1150. // Updating `Info.plist`
  1151. {
  1152. const String *K = nullptr;
  1153. while ((K = plist_values.next(K))) {
  1154. String key = *K;
  1155. String value = plist_values[key];
  1156. if (key.empty() || value.empty()) {
  1157. continue;
  1158. }
  1159. p_config_data.plist_content += "<key>" + key + "</key><string>" + value + "</string>\n";
  1160. }
  1161. }
  1162. // Export files
  1163. {
  1164. // Export linked plugin dependency
  1165. err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets);
  1166. ERR_FAIL_COND_V(err, err);
  1167. // Export embedded plugin dependency
  1168. err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets);
  1169. ERR_FAIL_COND_V(err, err);
  1170. // Export plugin files
  1171. err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets);
  1172. ERR_FAIL_COND_V(err, err);
  1173. }
  1174. // Update CPP
  1175. {
  1176. Dictionary plugin_format;
  1177. plugin_format["definition"] = plugin_definition_cpp_code;
  1178. plugin_format["initialization"] = plugin_initialization_cpp_code;
  1179. plugin_format["deinitialization"] = plugin_deinitialization_cpp_code;
  1180. String plugin_cpp_code = "\n// Godot Plugins\n"
  1181. "void godot_ios_plugins_initialize();\n"
  1182. "void godot_ios_plugins_deinitialize();\n"
  1183. "// Exported Plugins\n\n"
  1184. "$definition"
  1185. "// Use Plugins\n"
  1186. "void godot_ios_plugins_initialize() {\n"
  1187. "$initialization"
  1188. "}\n\n"
  1189. "void godot_ios_plugins_deinitialize() {\n"
  1190. "$deinitialization"
  1191. "}\n";
  1192. p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_");
  1193. }
  1194. // Update Linker Flag Values
  1195. {
  1196. String result_linker_flags = " ";
  1197. for (Set<String>::Element *E = plugin_linker_flags.front(); E; E = E->next()) {
  1198. const String &flag = E->get();
  1199. if (flag.length() == 0) {
  1200. continue;
  1201. }
  1202. if (result_linker_flags.length() > 0) {
  1203. result_linker_flags += ' ';
  1204. }
  1205. result_linker_flags += flag;
  1206. }
  1207. result_linker_flags = result_linker_flags.replace("\"", "\\\"");
  1208. p_config_data.linker_flags += result_linker_flags;
  1209. }
  1210. return OK;
  1211. }
  1212. Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
  1213. ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
  1214. String src_pkg_name;
  1215. String dest_dir = p_path.get_base_dir() + "/";
  1216. String binary_name = p_path.get_file().get_basename();
  1217. EditorProgress ep("export", "Exporting for iOS", 5, true);
  1218. String team_id = p_preset->get("application/app_store_team_id");
  1219. ERR_FAIL_COND_V_MSG(team_id.length() == 0, ERR_CANT_OPEN, "App Store Team ID not specified - cannot configure the project.");
  1220. if (p_debug) {
  1221. src_pkg_name = p_preset->get("custom_template/debug");
  1222. } else {
  1223. src_pkg_name = p_preset->get("custom_template/release");
  1224. }
  1225. if (src_pkg_name == "") {
  1226. String err;
  1227. src_pkg_name = find_export_template("iphone.zip", &err);
  1228. if (src_pkg_name == "") {
  1229. EditorNode::add_io_error(err);
  1230. return ERR_FILE_NOT_FOUND;
  1231. }
  1232. }
  1233. if (!DirAccess::exists(dest_dir)) {
  1234. return ERR_FILE_BAD_PATH;
  1235. }
  1236. DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  1237. if (da) {
  1238. String current_dir = da->get_current_dir();
  1239. // remove leftovers from last export so they don't interfere
  1240. // in case some files are no longer needed
  1241. if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) {
  1242. da->erase_contents_recursive();
  1243. }
  1244. if (da->change_dir(dest_dir + binary_name) == OK) {
  1245. da->erase_contents_recursive();
  1246. }
  1247. da->change_dir(current_dir);
  1248. if (!da->dir_exists(dest_dir + binary_name)) {
  1249. Error err = da->make_dir(dest_dir + binary_name);
  1250. if (err) {
  1251. memdelete(da);
  1252. return err;
  1253. }
  1254. }
  1255. memdelete(da);
  1256. }
  1257. if (ep.step("Making .pck", 0)) {
  1258. return ERR_SKIP;
  1259. }
  1260. String pack_path = dest_dir + binary_name + ".pck";
  1261. Vector<SharedObject> libraries;
  1262. Error err = save_pack(p_preset, pack_path, &libraries);
  1263. if (err) {
  1264. return err;
  1265. }
  1266. if (ep.step("Extracting and configuring Xcode project", 1)) {
  1267. return ERR_SKIP;
  1268. }
  1269. String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".xcframework";
  1270. print_line("Static framework: " + library_to_use);
  1271. String pkg_name;
  1272. if (p_preset->get("application/name") != "") {
  1273. pkg_name = p_preset->get("application/name"); // app_name
  1274. } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
  1275. pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name"));
  1276. } else {
  1277. pkg_name = "Unnamed";
  1278. }
  1279. bool found_library = false;
  1280. int total_size = 0;
  1281. const String project_file = "godot_ios.xcodeproj/project.pbxproj";
  1282. Set<String> files_to_parse;
  1283. files_to_parse.insert("godot_ios/godot_ios-Info.plist");
  1284. files_to_parse.insert(project_file);
  1285. files_to_parse.insert("godot_ios/export_options.plist");
  1286. files_to_parse.insert("godot_ios/dummy.cpp");
  1287. files_to_parse.insert("godot_ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata");
  1288. files_to_parse.insert("godot_ios.xcodeproj/xcshareddata/xcschemes/godot_ios.xcscheme");
  1289. files_to_parse.insert("godot_ios/godot_ios.entitlements");
  1290. files_to_parse.insert("godot_ios/Launch Screen.storyboard");
  1291. IOSConfigData config_data = {
  1292. pkg_name,
  1293. binary_name,
  1294. _get_additional_plist_content(),
  1295. String(" ").join(_get_preset_architectures(p_preset)),
  1296. _get_linker_flags(),
  1297. _get_cpp_code(),
  1298. "",
  1299. "",
  1300. "",
  1301. "",
  1302. Vector<String>()
  1303. };
  1304. Vector<IOSExportAsset> assets;
  1305. DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir);
  1306. ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE);
  1307. print_line("Unzipping...");
  1308. FileAccess *src_f = nullptr;
  1309. zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
  1310. unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
  1311. if (!src_pkg_zip) {
  1312. EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name);
  1313. return ERR_CANT_OPEN;
  1314. }
  1315. err = _export_ios_plugins(p_preset, config_data, dest_dir + binary_name, assets, p_debug);
  1316. ERR_FAIL_COND_V(err, err);
  1317. //export rest of the files
  1318. int ret = unzGoToFirstFile(src_pkg_zip);
  1319. Vector<uint8_t> project_file_data;
  1320. while (ret == UNZ_OK) {
  1321. #if defined(OSX_ENABLED) || defined(X11_ENABLED)
  1322. bool is_execute = false;
  1323. #endif
  1324. //get filename
  1325. unz_file_info info;
  1326. char fname[16384];
  1327. ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0);
  1328. String file = fname;
  1329. print_line("READ: " + file);
  1330. Vector<uint8_t> data;
  1331. data.resize(info.uncompressed_size);
  1332. //read
  1333. unzOpenCurrentFile(src_pkg_zip);
  1334. unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size());
  1335. unzCloseCurrentFile(src_pkg_zip);
  1336. //write
  1337. file = file.replace_first("iphone/", "");
  1338. if (files_to_parse.has(file)) {
  1339. _fix_config_file(p_preset, data, config_data, p_debug);
  1340. } else if (file.begins_with("libgodot.iphone")) {
  1341. if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) {
  1342. ret = unzGoToNextFile(src_pkg_zip);
  1343. continue; //ignore!
  1344. }
  1345. found_library = true;
  1346. #if defined(OSX_ENABLED) || defined(X11_ENABLED)
  1347. is_execute = true;
  1348. #endif
  1349. file = file.replace(library_to_use, binary_name + ".xcframework");
  1350. }
  1351. if (file == project_file) {
  1352. project_file_data = data;
  1353. }
  1354. ///@TODO need to parse logo files
  1355. if (data.size() > 0) {
  1356. file = file.replace("godot_ios", binary_name);
  1357. print_line("ADDING: " + file + " size: " + itos(data.size()));
  1358. total_size += data.size();
  1359. /* write it into our folder structure */
  1360. file = dest_dir + file;
  1361. /* make sure this folder exists */
  1362. String dir_name = file.get_base_dir();
  1363. if (!tmp_app_path->dir_exists(dir_name)) {
  1364. print_line("Creating " + dir_name);
  1365. Error dir_err = tmp_app_path->make_dir_recursive(dir_name);
  1366. if (dir_err) {
  1367. ERR_PRINTS("Can't create '" + dir_name + "'.");
  1368. unzClose(src_pkg_zip);
  1369. memdelete(tmp_app_path);
  1370. return ERR_CANT_CREATE;
  1371. }
  1372. }
  1373. /* write the file */
  1374. FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
  1375. if (!f) {
  1376. ERR_PRINTS("Can't write '" + file + "'.");
  1377. unzClose(src_pkg_zip);
  1378. memdelete(tmp_app_path);
  1379. return ERR_CANT_CREATE;
  1380. };
  1381. f->store_buffer(data.ptr(), data.size());
  1382. f->close();
  1383. memdelete(f);
  1384. #if defined(OSX_ENABLED) || defined(X11_ENABLED)
  1385. if (is_execute) {
  1386. // we need execute rights on this file
  1387. chmod(file.utf8().get_data(), 0755);
  1388. }
  1389. #endif
  1390. }
  1391. ret = unzGoToNextFile(src_pkg_zip);
  1392. }
  1393. /* we're done with our source zip */
  1394. unzClose(src_pkg_zip);
  1395. if (!found_library) {
  1396. ERR_PRINTS("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive.");
  1397. memdelete(tmp_app_path);
  1398. return ERR_FILE_NOT_FOUND;
  1399. }
  1400. // Copy project static libs to the project
  1401. Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
  1402. for (int i = 0; i < export_plugins.size(); i++) {
  1403. Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();
  1404. for (int j = 0; j < project_static_libs.size(); j++) {
  1405. const String &static_lib_path = project_static_libs[j];
  1406. String dest_lib_file_path = dest_dir + static_lib_path.get_file();
  1407. bool dir_exists = tmp_app_path->dir_exists(static_lib_path);
  1408. Error lib_copy_err = dir_exists ? tmp_app_path->copy_dir(static_lib_path, dest_lib_file_path) : tmp_app_path->copy(static_lib_path, dest_lib_file_path);
  1409. if (lib_copy_err != OK) {
  1410. ERR_PRINTS("Can't copy '" + static_lib_path + "'.");
  1411. memdelete(tmp_app_path);
  1412. return lib_copy_err;
  1413. }
  1414. }
  1415. }
  1416. String iconset_dir = dest_dir + binary_name + "/Images.xcassets/AppIcon.appiconset/";
  1417. err = OK;
  1418. if (!tmp_app_path->dir_exists(iconset_dir)) {
  1419. err = tmp_app_path->make_dir_recursive(iconset_dir);
  1420. }
  1421. memdelete(tmp_app_path);
  1422. if (err) {
  1423. return err;
  1424. }
  1425. err = _export_icons(p_preset, iconset_dir);
  1426. if (err) {
  1427. return err;
  1428. }
  1429. bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard");
  1430. String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/";
  1431. String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/";
  1432. DirAccess *launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  1433. if (!launch_screen_da) {
  1434. return ERR_CANT_CREATE;
  1435. }
  1436. if (use_storyboard) {
  1437. print_line("Using Launch Storyboard");
  1438. if (launch_screen_da->change_dir(launch_image_path) == OK) {
  1439. launch_screen_da->erase_contents_recursive();
  1440. launch_screen_da->remove(launch_image_path);
  1441. }
  1442. err = _export_loading_screen_file(p_preset, splash_image_path);
  1443. } else {
  1444. print_line("Using Launch Images");
  1445. const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard";
  1446. launch_screen_da->remove(launch_screen_path);
  1447. if (launch_screen_da->change_dir(splash_image_path) == OK) {
  1448. launch_screen_da->erase_contents_recursive();
  1449. launch_screen_da->remove(splash_image_path);
  1450. }
  1451. err = _export_loading_screen_images(p_preset, launch_image_path);
  1452. }
  1453. memdelete(launch_screen_da);
  1454. if (err) {
  1455. return err;
  1456. }
  1457. print_line("Exporting additional assets");
  1458. _export_additional_assets(dest_dir + binary_name, libraries, assets);
  1459. _add_assets_to_project(p_preset, project_file_data, assets);
  1460. String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj";
  1461. FileAccess *f = FileAccess::open(project_file_name, FileAccess::WRITE);
  1462. if (!f) {
  1463. ERR_PRINTS("Can't write '" + project_file_name + "'.");
  1464. return ERR_CANT_CREATE;
  1465. };
  1466. f->store_buffer(project_file_data.ptr(), project_file_data.size());
  1467. f->close();
  1468. memdelete(f);
  1469. #ifdef OSX_ENABLED
  1470. if (ep.step("Code-signing dylibs", 2)) {
  1471. return ERR_SKIP;
  1472. }
  1473. DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs");
  1474. ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN);
  1475. CodesignData codesign_data(p_preset, p_debug);
  1476. err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data);
  1477. memdelete(dylibs_dir);
  1478. ERR_FAIL_COND_V(err, err);
  1479. if (ep.step("Making .xcarchive", 3)) {
  1480. return ERR_SKIP;
  1481. }
  1482. String archive_path = p_path.get_basename() + ".xcarchive";
  1483. List<String> archive_args;
  1484. archive_args.push_back("-project");
  1485. archive_args.push_back(dest_dir + binary_name + ".xcodeproj");
  1486. archive_args.push_back("-scheme");
  1487. archive_args.push_back(binary_name);
  1488. archive_args.push_back("-sdk");
  1489. archive_args.push_back("iphoneos");
  1490. archive_args.push_back("-configuration");
  1491. archive_args.push_back(p_debug ? "Debug" : "Release");
  1492. archive_args.push_back("-destination");
  1493. archive_args.push_back("generic/platform=iOS");
  1494. archive_args.push_back("archive");
  1495. archive_args.push_back("-archivePath");
  1496. archive_args.push_back(archive_path);
  1497. err = OS::get_singleton()->execute("xcodebuild", archive_args, true);
  1498. ERR_FAIL_COND_V(err, err);
  1499. if (ep.step("Making .ipa", 4)) {
  1500. return ERR_SKIP;
  1501. }
  1502. List<String> export_args;
  1503. export_args.push_back("-exportArchive");
  1504. export_args.push_back("-archivePath");
  1505. export_args.push_back(archive_path);
  1506. export_args.push_back("-exportOptionsPlist");
  1507. export_args.push_back(dest_dir + binary_name + "/export_options.plist");
  1508. export_args.push_back("-allowProvisioningUpdates");
  1509. export_args.push_back("-exportPath");
  1510. export_args.push_back(dest_dir);
  1511. err = OS::get_singleton()->execute("xcodebuild", export_args, true);
  1512. ERR_FAIL_COND_V(err, err);
  1513. #else
  1514. print_line(".ipa can only be built on macOS. Leaving Xcode project without building the package.");
  1515. #endif
  1516. return OK;
  1517. }
  1518. bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
  1519. String err;
  1520. bool valid = false;
  1521. // Look for export templates (first official, and if defined custom templates).
  1522. bool dvalid = exists_export_template("iphone.zip", &err);
  1523. bool rvalid = dvalid; // Both in the same ZIP.
  1524. if (p_preset->get("custom_template/debug") != "") {
  1525. dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
  1526. if (!dvalid) {
  1527. err += TTR("Custom debug template not found.") + "\n";
  1528. }
  1529. }
  1530. if (p_preset->get("custom_template/release") != "") {
  1531. rvalid = FileAccess::exists(p_preset->get("custom_template/release"));
  1532. if (!rvalid) {
  1533. err += TTR("Custom release template not found.") + "\n";
  1534. }
  1535. }
  1536. valid = dvalid || rvalid;
  1537. r_missing_templates = !valid;
  1538. // Validate the rest of the configuration.
  1539. String team_id = p_preset->get("application/app_store_team_id");
  1540. if (team_id.length() == 0) {
  1541. err += TTR("App Store Team ID not specified - cannot configure the project.") + "\n";
  1542. valid = false;
  1543. }
  1544. String identifier = p_preset->get("application/identifier");
  1545. String pn_err;
  1546. if (!is_package_name_valid(identifier, &pn_err)) {
  1547. err += TTR("Invalid Identifier:") + " " + pn_err + "\n";
  1548. valid = false;
  1549. }
  1550. for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) {
  1551. IconInfo info = icon_infos[i];
  1552. String icon_path = p_preset->get(info.preset_key);
  1553. if (icon_path.length() == 0) {
  1554. if (info.is_required) {
  1555. err += TTR("Required icon is not specified in the preset.") + "\n";
  1556. valid = false;
  1557. }
  1558. break;
  1559. }
  1560. }
  1561. String etc_error = test_etc2_or_pvrtc();
  1562. if (etc_error != String()) {
  1563. valid = false;
  1564. err += etc_error;
  1565. }
  1566. if (!err.empty()) {
  1567. r_error = err;
  1568. }
  1569. return valid;
  1570. }
  1571. EditorExportPlatformIOS::EditorExportPlatformIOS() {
  1572. Ref<Image> img = memnew(Image(_iphone_logo));
  1573. logo.instance();
  1574. logo->create_from_image(img);
  1575. plugins_changed.set();
  1576. check_for_changes_thread.start(_check_for_changes_poll_thread, this);
  1577. }
  1578. EditorExportPlatformIOS::~EditorExportPlatformIOS() {
  1579. quit_request.set();
  1580. check_for_changes_thread.wait_to_finish();
  1581. }
  1582. void register_iphone_exporter() {
  1583. Ref<EditorExportPlatformIOS> platform;
  1584. platform.instance();
  1585. EditorExport::get_singleton()->add_export_platform(platform);
  1586. }