From dd03dcbd5a1d4a23d4fb3aa41da0c91fe2c5eda5 Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Sun, 7 Apr 2019 15:46:52 -0300 Subject: [PATCH] Android now (optionally) builds the template when exporting Added new way to create add-ons Removed old way to create add-ons --- .gitignore | 3 - SConstruct | 28 -- editor/editor_node.cpp | 111 ++++- editor/editor_node.h | 21 + editor/export_template_manager.cpp | 113 ++++- editor/export_template_manager.h | 3 + methods.py | 64 --- platform/android/SCsub | 108 ----- platform/android/build.gradle.template | 88 ---- platform/android/export/export.cpp | 403 ++++++++++++++++-- .../AndroidManifest.xml} | 26 +- platform/android/java/build.gradle | 115 +++++ platform/windows/os_windows.cpp | 8 +- platform/windows/os_windows.h | 2 +- 14 files changed, 766 insertions(+), 327 deletions(-) delete mode 100644 platform/android/build.gradle.template rename platform/android/{AndroidManifest.xml.template => java/AndroidManifest.xml} (55%) create mode 100644 platform/android/java/build.gradle diff --git a/.gitignore b/.gitignore index 6acab19251e..2697507adba 100644 --- a/.gitignore +++ b/.gitignore @@ -8,13 +8,10 @@ doc/_build/ *.bc # Android specific -platform/android/java/build.gradle platform/android/java/.gradle platform/android/java/.gradletasknamecache platform/android/java/local.properties platform/android/java/project.properties -platform/android/java/build.gradle -platform/android/java/AndroidManifest.xml platform/android/java/libs/* platform/android/java/assets platform/android/java/.idea/* diff --git a/SConstruct b/SConstruct index 8e6795cbcb0..6bb129a1746 100644 --- a/SConstruct +++ b/SConstruct @@ -66,20 +66,6 @@ if 'TERM' in os.environ: env_base['ENV']['TERM'] = os.environ['TERM'] env_base.AppendENVPath('PATH', os.getenv('PATH')) env_base.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH')) -env_base.android_maven_repos = [] -env_base.android_flat_dirs = [] -env_base.android_dependencies = [] -env_base.android_gradle_plugins = [] -env_base.android_gradle_classpath = [] -env_base.android_java_dirs = [] -env_base.android_res_dirs = [] -env_base.android_asset_dirs = [] -env_base.android_aidl_dirs = [] -env_base.android_jni_dirs = [] -env_base.android_default_config = [] -env_base.android_manifest_chunk = "" -env_base.android_permission_chunk = "" -env_base.android_appattributes_chunk = "" env_base.disabled_modules = [] env_base.use_ptrcall = False env_base.split_drivers = False @@ -87,20 +73,6 @@ env_base.split_modules = False env_base.module_version_string = "" env_base.msvc = False -env_base.__class__.android_add_maven_repository = methods.android_add_maven_repository -env_base.__class__.android_add_flat_dir = methods.android_add_flat_dir -env_base.__class__.android_add_dependency = methods.android_add_dependency -env_base.__class__.android_add_java_dir = methods.android_add_java_dir -env_base.__class__.android_add_res_dir = methods.android_add_res_dir -env_base.__class__.android_add_asset_dir = methods.android_add_asset_dir -env_base.__class__.android_add_aidl_dir = methods.android_add_aidl_dir -env_base.__class__.android_add_jni_dir = methods.android_add_jni_dir -env_base.__class__.android_add_default_config = methods.android_add_default_config -env_base.__class__.android_add_to_manifest = methods.android_add_to_manifest -env_base.__class__.android_add_to_permissions = methods.android_add_to_permissions -env_base.__class__.android_add_to_attributes = methods.android_add_to_attributes -env_base.__class__.android_add_gradle_plugin = methods.android_add_gradle_plugin -env_base.__class__.android_add_gradle_classpath = methods.android_add_gradle_classpath env_base.__class__.disable_module = methods.disable_module env_base.__class__.add_module_version_string = methods.add_module_version_string diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index f7a952d5cc2..477a6b7f73c 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -47,6 +47,7 @@ #include "core/translation.h" #include "core/version.h" #include "main/input_default.h" +#include "main/main.h" #include "scene/resources/packed_scene.h" #include "servers/physics_2d_server.h" @@ -2270,6 +2271,23 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { OS::get_singleton()->shell_open(String("file://") + OS::get_singleton()->get_user_data_dir()); } break; + case FILE_INSTALL_ANDROID_SOURCE: { + + if (p_confirmed) { + export_template_manager->install_android_template(); + } else { + if (DirAccess::exists("res://android/build")) { + remove_android_build_template->popup_centered_minsize(); + } else if (export_template_manager->can_install_android_template()) { + install_android_build_template->popup_centered_minsize(); + } else { + custom_build_manage_templates->popup_centered_minsize(); + } + } + } break; + case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: { + OS::get_singleton()->shell_open(String("file://") + ProjectSettings::get_singleton()->get_resource_path().plus_file("android")); + } break; case FILE_QUIT: case RUN_PROJECT_MANAGER: { @@ -5044,6 +5062,68 @@ void EditorNode::_print_handler(void *p_this, const String &p_string, bool p_err en->log->add_message(p_string, p_error ? EditorLog::MSG_TYPE_ERROR : EditorLog::MSG_TYPE_STD); } +static void _execute_thread(void *p_ud) { + + EditorNode::ExecuteThreadArgs *eta = (EditorNode::ExecuteThreadArgs *)p_ud; + Error err = OS::get_singleton()->execute(eta->path, eta->args, true, NULL, &eta->output, &eta->exitcode, true, eta->execute_output_mutex); + print_verbose("Thread exit status: " + itos(eta->exitcode)); + if (err != OK) { + eta->exitcode = err; + } + + eta->done = true; +} + +int EditorNode::execute_and_show_output(const String &p_title, const String &p_path, const List &p_arguments, bool p_close_on_ok, bool p_close_on_errors) { + + execute_output_dialog->set_title(p_title); + execute_output_dialog->get_ok()->set_disabled(true); + execute_outputs->clear(); + execute_outputs->set_scroll_follow(true); + execute_output_dialog->popup_centered_ratio(); + + ExecuteThreadArgs eta; + eta.path = p_path; + eta.args = p_arguments; + eta.execute_output_mutex = Mutex::create(); + eta.exitcode = 255; + eta.done = false; + + int prev_len = 0; + + eta.execute_output_thread = Thread::create(_execute_thread, &eta); + + ERR_FAIL_COND_V(!eta.execute_output_thread, 0); + + while (!eta.done) { + eta.execute_output_mutex->lock(); + if (prev_len != eta.output.length()) { + String to_add = eta.output.substr(prev_len, eta.output.length()); + prev_len = eta.output.length(); + execute_outputs->add_text(to_add); + Main::iteration(); + } + eta.execute_output_mutex->unlock(); + OS::get_singleton()->delay_usec(1000); + } + + Thread::wait_to_finish(eta.execute_output_thread); + memdelete(eta.execute_output_thread); + memdelete(eta.execute_output_mutex); + execute_outputs->add_text("\nExit Code: " + itos(eta.exitcode)); + + if (p_close_on_errors && eta.exitcode != 0) { + execute_output_dialog->hide(); + } + if (p_close_on_ok && eta.exitcode == 0) { + execute_output_dialog->hide(); + } + + execute_output_dialog->get_ok()->set_disabled(false); + + return eta.exitcode; +} + EditorNode::EditorNode() { Input::get_singleton()->set_use_accumulated_input(true); @@ -5618,12 +5698,13 @@ EditorNode::EditorNode() { tool_menu = memnew(PopupMenu); tool_menu->set_name("Tools"); tool_menu->connect("index_pressed", this, "_tool_menu_option"); + p->add_separator(); p->add_child(tool_menu); p->add_submenu_item(TTR("Tools"), "Tools"); - tool_menu->add_shortcut(ED_SHORTCUT("editor/orphan_resource_explorer", TTR("Orphan Resource Explorer")), TOOLS_ORPHAN_RESOURCES); + tool_menu->add_item(TTR("Orphan Resource Explorer"), TOOLS_ORPHAN_RESOURCES); + tool_menu->add_item(TTR("Open Project Data Folder"), RUN_PROJECT_DATA_FOLDER); p->add_separator(); - - p->add_shortcut(ED_SHORTCUT("editor/open_project_data_folder", TTR("Open Project Data Folder")), RUN_PROJECT_DATA_FOLDER); + p->add_item(TTR("Install Android Build Template"), FILE_INSTALL_ANDROID_SOURCE); p->add_separator(); #ifdef OSX_ENABLED @@ -5970,6 +6051,24 @@ EditorNode::EditorNode() { save_confirmation->connect("confirmed", this, "_menu_confirm_current"); save_confirmation->connect("custom_action", this, "_discard_changes"); + custom_build_manage_templates = memnew(ConfirmationDialog); + custom_build_manage_templates->set_text(TTR("Android build template is missing, please install relevant templates.")); + custom_build_manage_templates->get_ok()->set_text(TTR("Manage Templates")); + custom_build_manage_templates->connect("confirmed", this, "_menu_option", varray(SETTINGS_MANAGE_EXPORT_TEMPLATES)); + gui_base->add_child(custom_build_manage_templates); + + install_android_build_template = memnew(ConfirmationDialog); + install_android_build_template->set_text(TTR("This will install the Android project for custom builds.\nNote that, in order to use it, it needs to be enabled per export preset.")); + install_android_build_template->get_ok()->set_text(TTR("Install")); + install_android_build_template->connect("confirmed", this, "_menu_confirm_current"); + gui_base->add_child(install_android_build_template); + + remove_android_build_template = memnew(ConfirmationDialog); + remove_android_build_template->set_text(TTR("Android build template is already installed and it won't be overwritten.\nRemove the \"build\" directory manually before attempting this operation again.")); + remove_android_build_template->get_ok()->set_text(TTR("Show in File Manager")); + remove_android_build_template->connect("confirmed", this, "_menu_option", varray(FILE_EXPLORE_ANDROID_BUILD_TEMPLATES)); + gui_base->add_child(remove_android_build_template); + file_templates = memnew(EditorFileDialog); file_templates->set_title(TTR("Import Templates From ZIP File")); @@ -6184,6 +6283,12 @@ EditorNode::EditorNode() { load_error_dialog->set_title(TTR("Load Errors")); gui_base->add_child(load_error_dialog); + execute_outputs = memnew(RichTextLabel); + execute_output_dialog = memnew(AcceptDialog); + execute_output_dialog->add_child(execute_outputs); + execute_output_dialog->set_title(TTR("")); + gui_base->add_child(execute_output_dialog); + EditorFileSystem::get_singleton()->connect("sources_changed", this, "_sources_changed"); EditorFileSystem::get_singleton()->connect("filesystem_changed", this, "_fs_changed"); EditorFileSystem::get_singleton()->connect("resources_reimported", this, "_resources_reimported"); diff --git a/editor/editor_node.h b/editor/editor_node.h index 328986fc696..a06708eb541 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -112,6 +112,16 @@ public: DOCK_SLOT_MAX }; + struct ExecuteThreadArgs { + String path; + List args; + String output; + Thread *execute_output_thread; + Mutex *execute_output_mutex; + int exitcode; + volatile bool done; + }; + private: enum { HISTORY_SIZE = 64 @@ -130,6 +140,8 @@ private: FILE_IMPORT_SUBSCENE, FILE_EXPORT_PROJECT, FILE_EXPORT_MESH_LIBRARY, + FILE_INSTALL_ANDROID_SOURCE, + FILE_EXPLORE_ANDROID_BUILD_TEMPLATES, FILE_EXPORT_TILESET, FILE_SAVE_OPTIMIZED, FILE_OPEN_RECENT, @@ -267,6 +279,9 @@ private: RichTextLabel *load_errors; AcceptDialog *load_error_dialog; + RichTextLabel *execute_outputs; + AcceptDialog *execute_output_dialog; + Ref theme; PopupMenu *recent_scenes; @@ -290,6 +305,10 @@ private: PopupMenu *editor_layouts; EditorNameDialog *layout_dialog; + ConfirmationDialog *custom_build_manage_templates; + ConfirmationDialog *install_android_build_template; + ConfirmationDialog *remove_android_build_template; + EditorSettingsDialog *settings_config_dialog; RunSettingsDialog *run_settings_dialog; ProjectSettingsEditor *project_settings; @@ -800,6 +819,8 @@ public: void update_keying() const { inspector_dock->update_keying(); }; bool has_scenes_in_session(); + int execute_and_show_output(const String &p_title, const String &p_path, const List &p_arguments, bool p_close_on_ok = true, bool p_close_on_errors = false); + EditorNode(); ~EditorNode(); void get_singleton(const char *arg1, bool arg2); diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index 97ccfb0db1c..aa2a03510d9 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -308,7 +308,8 @@ bool ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_ p->step(TTR("Importing:") + " " + file, fc); } - FileAccess *f = FileAccess::open(template_path.plus_file(file), FileAccess::WRITE); + String to_write = template_path.plus_file(file); + FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE); if (!f) { ret = unzGoToNextFile(pkg); @@ -320,6 +321,10 @@ bool ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_ memdelete(f); +#ifndef WINDOWS_ENABLED + FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF); +#endif + ret = unzGoToNextFile(pkg); fc++; } @@ -541,6 +546,112 @@ void ExportTemplateManager::_notification(int p_what) { } } +bool ExportTemplateManager::can_install_android_template() { + + return FileAccess::exists(EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG).plus_file("android_source.zip")); +} + +Error ExportTemplateManager::install_android_template() { + + DirAccessRef da = DirAccess::open("res://"); + ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); + //make android dir (if it does not exist) + + da->make_dir("android"); + { + //add an empty .gdignore file to avoid scan + FileAccessRef f = FileAccess::open("res://android/.gdignore", FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); + f->store_line(""); + f->close(); + } + { + //add version, to ensure building wont work if template and Godot version are mismatch + FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); + f->store_line(VERSION_FULL_CONFIG); + f->close(); + } + + Error err = da->make_dir_recursive("android/build"); + ERR_FAIL_COND_V(err != OK, err); + + String source_zip = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG).plus_file("android_source.zip"); + ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN); + + FileAccess *src_f = NULL; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + + unzFile pkg = unzOpen2(source_zip.utf8().get_data(), &io); + ERR_EXPLAIN("Android sources not in zip format"); + ERR_FAIL_COND_V(!pkg, ERR_CANT_OPEN); + + int ret = unzGoToFirstFile(pkg); + + int total_files = 0; + //count files + while (ret == UNZ_OK) { + total_files++; + ret = unzGoToNextFile(pkg); + } + + ret = unzGoToFirstFile(pkg); + //decompress files + ProgressDialog::get_singleton()->add_task("uncompress", TTR("Uncompressing Android Build Sources"), total_files); + + Set dirs_tested; + + int idx = 0; + while (ret == UNZ_OK) { + + //get filename + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0); + + String name = fname; + + String base_dir = name.get_base_dir(); + + if (!name.ends_with("/")) { + Vector data; + data.resize(info.uncompressed_size); + + //read + unzOpenCurrentFile(pkg); + unzReadCurrentFile(pkg, data.ptrw(), data.size()); + unzCloseCurrentFile(pkg); + + if (!dirs_tested.has(base_dir)) { + da->make_dir_recursive(String("android/build").plus_file(base_dir)); + dirs_tested.insert(base_dir); + } + + String to_write = String("res://android/build").plus_file(name); + FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE); + if (f) { + f->store_buffer(data.ptr(), data.size()); + memdelete(f); +#ifndef WINDOWS_ENABLED + FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF); +#endif + } else { + ERR_PRINTS("Cant uncompress file: " + to_write); + } + } + + ProgressDialog::get_singleton()->task_step("uncompress", name, idx); + + idx++; + ret = unzGoToNextFile(pkg); + } + + ProgressDialog::get_singleton()->end_task("uncompress"); + unzClose(pkg); + + return OK; +} + void ExportTemplateManager::_bind_methods() { ClassDB::bind_method("_download_template", &ExportTemplateManager::_download_template); diff --git a/editor/export_template_manager.h b/editor/export_template_manager.h index 2edd3db6d70..608830c990f 100644 --- a/editor/export_template_manager.h +++ b/editor/export_template_manager.h @@ -84,6 +84,9 @@ protected: static void _bind_methods(); public: + bool can_install_android_template(); + Error install_android_template(); + void popup_manager(); ExportTemplateManager(); diff --git a/methods.py b/methods.py index ec9ecdb17fb..d2bad7f9dd8 100644 --- a/methods.py +++ b/methods.py @@ -211,70 +211,6 @@ def win32_spawn(sh, escape, cmd, args, spawnenv): return exit_code """ -def android_add_flat_dir(self, dir): - if (dir not in self.android_flat_dirs): - self.android_flat_dirs.append(dir) - -def android_add_maven_repository(self, url): - if (url not in self.android_maven_repos): - self.android_maven_repos.append(url) - -def android_add_dependency(self, depline): - if (depline not in self.android_dependencies): - self.android_dependencies.append(depline) - -def android_add_java_dir(self, subpath): - base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath - if (base_path not in self.android_java_dirs): - self.android_java_dirs.append(base_path) - -def android_add_res_dir(self, subpath): - base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath - if (base_path not in self.android_res_dirs): - self.android_res_dirs.append(base_path) - -def android_add_asset_dir(self, subpath): - base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath - if (base_path not in self.android_asset_dirs): - self.android_asset_dirs.append(base_path) - -def android_add_aidl_dir(self, subpath): - base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath - if (base_path not in self.android_aidl_dirs): - self.android_aidl_dirs.append(base_path) - -def android_add_jni_dir(self, subpath): - base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath - if (base_path not in self.android_jni_dirs): - self.android_jni_dirs.append(base_path) - -def android_add_gradle_plugin(self, plugin): - if (plugin not in self.android_gradle_plugins): - self.android_gradle_plugins.append(plugin) - -def android_add_gradle_classpath(self, classpath): - if (classpath not in self.android_gradle_classpath): - self.android_gradle_classpath.append(classpath) - -def android_add_default_config(self, config): - if (config not in self.android_default_config): - self.android_default_config.append(config) - -def android_add_to_manifest(self, file): - base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + file - with open(base_path, "r") as f: - self.android_manifest_chunk += f.read() - -def android_add_to_permissions(self, file): - base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + file - with open(base_path, "r") as f: - self.android_permission_chunk += f.read() - -def android_add_to_attributes(self, file): - base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + file - with open(base_path, "r") as f: - self.android_appattributes_chunk += f.read() - def disable_module(self): self.disabled_modules.append(self.current_module) diff --git a/platform/android/SCsub b/platform/android/SCsub index d494372bcd2..22ed476c6f5 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -34,114 +34,6 @@ env_thirdparty = env_android.Clone() env_thirdparty.disable_warnings() android_objects.append(env_thirdparty.SharedObject('#thirdparty/misc/ifaddrs-android.cc')) -abspath = env.Dir(".").abspath - -with open_utf8(abspath + "/build.gradle.template", "r") as gradle_basein: - gradle_text = gradle_basein.read() - -gradle_maven_flat_text = "" -if len(env.android_flat_dirs) > 0: - gradle_maven_flat_text += "flatDir {\n" - gradle_maven_flat_text += "\tdirs " - for x in env.android_flat_dirs: - gradle_maven_flat_text += "'" + x + "'," - - gradle_maven_flat_text = gradle_maven_flat_text[:-1] - gradle_maven_flat_text += "\n\t}\n" - -gradle_maven_repos_text = "" -gradle_maven_repos_text += gradle_maven_flat_text - -if len(env.android_maven_repos) > 0: - gradle_maven_repos_text += "" - for x in env.android_maven_repos: - gradle_maven_repos_text += "\tmaven {\n" - gradle_maven_repos_text += "\t" + x + "\n" - gradle_maven_repos_text += "\t}\n" - -gradle_maven_dependencies_text = "" - -for x in env.android_dependencies: - gradle_maven_dependencies_text += x + "\n\t" - -gradle_java_dirs_text = "" - -for x in env.android_java_dirs: - gradle_java_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_plugins = "" -for x in env.android_gradle_plugins: - gradle_plugins += "apply plugin: \"" + x + "\"\n" - -gradle_classpath = "" -for x in env.android_gradle_classpath: - gradle_classpath += "\t\tclasspath \"" + x + "\"\n" - -gradle_res_dirs_text = "" - -for x in env.android_res_dirs: - gradle_res_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_aidl_dirs_text = "" - -for x in env.android_aidl_dirs: - gradle_aidl_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_jni_dirs_text = "" - -for x in env.android_jni_dirs: - gradle_jni_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_asset_dirs_text = "" - -for x in env.android_asset_dirs: - gradle_asset_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_default_config_text = "" - -minSdk = 18 -targetSdk = 28 - -for x in env.android_default_config: - if x.startswith("minSdkVersion") and int(x.split(" ")[-1]) < minSdk: - x = "minSdkVersion " + str(minSdk) - if x.startswith("targetSdkVersion") and int(x.split(" ")[-1]) > targetSdk: - x = "targetSdkVersion " + str(targetSdk) - - gradle_default_config_text += x + "\n\t\t" - -if "minSdkVersion" not in gradle_default_config_text: - gradle_default_config_text += ("minSdkVersion " + str(minSdk) + "\n\t\t") - -if "targetSdkVersion" not in gradle_default_config_text: - gradle_default_config_text += ("targetSdkVersion " + str(targetSdk) + "\n\t\t") - -gradle_text = gradle_text.replace("$$GRADLE_REPOSITORY_URLS$$", gradle_maven_repos_text) -gradle_text = gradle_text.replace("$$GRADLE_DEPENDENCIES$$", gradle_maven_dependencies_text) -gradle_text = gradle_text.replace("$$GRADLE_JAVA_DIRS$$", gradle_java_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_RES_DIRS$$", gradle_res_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_ASSET_DIRS$$", gradle_asset_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_AIDL_DIRS$$", gradle_aidl_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_JNI_DIRS$$", gradle_jni_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_DEFAULT_CONFIG$$", gradle_default_config_text) -gradle_text = gradle_text.replace("$$GRADLE_PLUGINS$$", gradle_plugins) -gradle_text = gradle_text.replace("$$GRADLE_CLASSPATH$$", gradle_classpath) - -with open_utf8(abspath + "/java/build.gradle", "w") as gradle_baseout: - gradle_baseout.write(gradle_text) - - -with open_utf8(abspath + "/AndroidManifest.xml.template", "r") as pp_basein: - manifest = pp_basein.read() - -manifest = manifest.replace("$$ADD_APPLICATION_CHUNKS$$", env.android_manifest_chunk) -manifest = manifest.replace("$$ADD_PERMISSION_CHUNKS$$", env.android_permission_chunk) -manifest = manifest.replace("$$ADD_APPATTRIBUTE_CHUNKS$$", env.android_appattributes_chunk) - -with open_utf8(abspath + "/java/AndroidManifest.xml", "w") as pp_baseout: - pp_baseout.write(manifest) - - lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"]) lib_arch_dir = '' diff --git a/platform/android/build.gradle.template b/platform/android/build.gradle.template deleted file mode 100644 index 2fea2500614..00000000000 --- a/platform/android/build.gradle.template +++ /dev/null @@ -1,88 +0,0 @@ -buildscript { - repositories { - google() - jcenter() - $$GRADLE_REPOSITORY_URLS$$ - } - dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' - $$GRADLE_CLASSPATH$$ - } -} - -apply plugin: 'com.android.application' - -allprojects { - repositories { - mavenCentral() - google() - jcenter() - $$GRADLE_REPOSITORY_URLS$$ - } -} - -dependencies { - implementation "com.android.support:support-core-utils:28.0.0" - $$GRADLE_DEPENDENCIES$$ -} - -android { - - lintOptions { - abortOnError false - disable 'MissingTranslation','UnusedResources' - } - - compileSdkVersion 28 - buildToolsVersion "28.0.3" - useLibrary 'org.apache.http.legacy' - - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/NOTICE' - } - defaultConfig { - $$GRADLE_DEFAULT_CONFIG$$ - } - // Both signing and zip-aligning will be done at export time - buildTypes.all { buildType -> - buildType.zipAlignEnabled false - buildType.signingConfig null - } - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src' - $$GRADLE_JAVA_DIRS$$ - ] - res.srcDirs = [ - 'res' - $$GRADLE_RES_DIRS$$ - ] - aidl.srcDirs = [ - 'aidl' - $$GRADLE_AIDL_DIRS$$ - ] - assets.srcDirs = [ - 'assets' - $$GRADLE_ASSET_DIRS$$ - ] - } - debug.jniLibs.srcDirs = [ - 'libs/debug' - $$GRADLE_JNI_DIRS$$ - ] - release.jniLibs.srcDirs = [ - 'libs/release' - $$GRADLE_JNI_DIRS$$ - ] - } - - applicationVariants.all { variant -> - variant.outputs.all { output -> - output.outputFileName = "../../../../../../../bin/android_${variant.name}.apk" - } - } -} - -$$GRADLE_PLUGINS$$ diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index afccbd113ea..f70cee2964c 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -417,6 +417,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { name = "noname"; pname = pname.replace("$genname", name); + return pname; } @@ -1143,11 +1144,12 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_package/use_custom_build"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.$genname"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "screen/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait"), 0)); @@ -1388,21 +1390,25 @@ public: virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { String err; - r_missing_templates = find_export_template("android_debug.apk") == String() || find_export_template("android_release.apk") == String(); - if (p_preset->get("custom_package/debug") != "") { - if (FileAccess::exists(p_preset->get("custom_package/debug"))) { - r_missing_templates = false; - } else { - err += TTR("Custom debug template not found.") + "\n"; + if (!bool(p_preset->get("custom_package/use_custom_build"))) { + + r_missing_templates = find_export_template("android_debug.apk") == String() || find_export_template("android_release.apk") == String(); + + if (p_preset->get("custom_package/debug") != "") { + if (FileAccess::exists(p_preset->get("custom_package/debug"))) { + r_missing_templates = false; + } else { + err += TTR("Custom debug template not found.") + "\n"; + } } - } - if (p_preset->get("custom_package/release") != "") { - if (FileAccess::exists(p_preset->get("custom_package/release"))) { - r_missing_templates = false; - } else { - err += TTR("Custom release template not found.") + "\n"; + if (p_preset->get("custom_package/release") != "") { + if (FileAccess::exists(p_preset->get("custom_package/release"))) { + r_missing_templates = false; + } else { + err += TTR("Custom release template not found.") + "\n"; + } } } @@ -1435,6 +1441,30 @@ public: } } + if (bool(p_preset->get("custom_package/use_custom_build"))) { + String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path"); + if (sdk_path == "") { + err += TTR("Custom build requires a valid Android SDK path in Editor Settings.") + "\n"; + valid = false; + } else { + Error errn; + DirAccess *da = DirAccess::open(sdk_path.plus_file("tools"), &errn); + if (errn != OK) { + err += TTR("Invalid Android SDK path for custom build in Editor Settings.") + "\n"; + valid = false; + } + if (da) { + memdelete(da); + } + } + + if (!FileAccess::exists("res://android/build/build.gradle")) { + + err += TTR("Android project is not installed for compiling. Install from Editor menu.") + "\n"; + valid = false; + } + } + bool apk_expansion = p_preset->get("apk_expansion/enable"); if (apk_expansion) { @@ -1473,6 +1503,260 @@ public: return list; } + void _update_custom_build_project() { + + DirAccessRef da = DirAccess::open("res://android"); + + ERR_FAIL_COND(!da); + Map > directory_paths; + Map > manifest_sections; + Map > gradle_sections; + da->list_dir_begin(); + String d = da->get_next(); + while (d != String()) { + + if (!d.begins_with(".") && d != "build" && da->current_is_dir()) { //a dir and not the build dir + //add directories found + DirAccessRef ds = DirAccess::open(String("res://android").plus_file(d)); + if (ds) { + ds->list_dir_begin(); + String sd = ds->get_next(); + while (sd != String()) { + + if (!sd.begins_with(".") && ds->current_is_dir()) { + String key = sd.to_upper(); + if (!directory_paths.has(key)) { + directory_paths[key] = List(); + } + String path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android").plus_file(d).plus_file(sd); + directory_paths[key].push_back(path); + print_line("Add: " + sd + ":" + path); + } + + sd = ds->get_next(); + } + ds->list_dir_end(); + } + //parse manifest + { + FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("AndroidManifest.conf"), FileAccess::READ); + if (f) { + + String section; + while (!f->eof_reached()) { + String l = f->get_line(); + String k = l.strip_edges(); + if (k.begins_with("[")) { + section = k.substr(1, k.length() - 2).strip_edges().to_upper(); + print_line("Section: " + section); + } else if (k != String()) { + if (!manifest_sections.has(section)) { + manifest_sections[section] = List(); + } + manifest_sections[section].push_back(l); + } + } + + f->close(); + } + } + //parse gradle + { + FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("gradle.conf"), FileAccess::READ); + if (f) { + + String section; + while (!f->eof_reached()) { + String l = f->get_line().strip_edges(); + String k = l.strip_edges(); + if (k.begins_with("[")) { + section = k.substr(1, k.length() - 2).strip_edges().to_upper(); + print_line("Section: " + section); + } else if (k != String()) { + if (!gradle_sections.has(section)) { + gradle_sections[section] = List(); + } + gradle_sections[section].push_back(l); + } + } + } + } + } + d = da->get_next(); + } + da->list_dir_end(); + + { //fix gradle build + + String new_file; + { + FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::READ); + if (f) { + + while (!f->eof_reached()) { + String l = f->get_line(); + + if (l.begins_with("//CHUNK_")) { + String text = l.replace_first("//CHUNK_", ""); + int begin_pos = text.find("_BEGIN"); + if (begin_pos != -1) { + text = text.substr(0, begin_pos); + text = text.to_upper(); //just in case + + String end_marker = "//CHUNK_" + text + "_END"; + size_t pos = f->get_position(); + bool found = false; + while (!f->eof_reached()) { + l = f->get_line(); + if (l.begins_with(end_marker)) { + found = true; + break; + } + } + + new_file += "//CHUNK_" + text + "_BEGIN\n"; + + if (!found) { + ERR_PRINTS("No end marker found in build.gradle for chunk: " + text); + f->seek(pos); + } else { + + //add chunk lines + if (gradle_sections.has(text)) { + for (List::Element *E = gradle_sections[text].front(); E; E = E->next()) { + new_file += E->get() + "\n"; + } + } + new_file += end_marker + "\n"; + } + } else { + new_file += l + "\n"; //pass line by + } + } else if (l.begins_with("//DIR_")) { + String text = l.replace_first("//DIR_", ""); + int begin_pos = text.find("_BEGIN"); + if (begin_pos != -1) { + text = text.substr(0, begin_pos); + text = text.to_upper(); //just in case + + String end_marker = "//DIR_" + text + "_END"; + size_t pos = f->get_position(); + bool found = false; + while (!f->eof_reached()) { + l = f->get_line(); + if (l.begins_with(end_marker)) { + found = true; + break; + } + } + + new_file += "//DIR_" + text + "_BEGIN\n"; + + if (!found) { + ERR_PRINTS("No end marker found in build.gradle for dir: " + text); + f->seek(pos); + } else { + //add chunk lines + if (directory_paths.has(text)) { + for (List::Element *E = directory_paths[text].front(); E; E = E->next()) { + new_file += ",'" + E->get().replace("'", "\'") + "'"; + new_file += "\n"; + } + } + new_file += end_marker + "\n"; + } + } else { + new_file += l + "\n"; //pass line by + } + + } else { + new_file += l + "\n"; + } + } + } + } + + FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::WRITE); + f->store_string(new_file); + f->close(); + } + + { //fix manifest + + String new_file; + { + FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::READ); + if (f) { + + while (!f->eof_reached()) { + String l = f->get_line(); + + if (l.begins_with(""); + if (begin_pos != -1) { + text = text.substr(0, begin_pos); + text = text.to_upper(); //just in case + + String end_marker = ""; + size_t pos = f->get_position(); + bool found = false; + while (!f->eof_reached()) { + l = f->get_line(); + if (l.begins_with(end_marker)) { + found = true; + break; + } + } + + new_file += "\n"; + + if (!found) { + ERR_PRINTS("No end marker found in AndroidManifest.conf for chunk: " + text); + f->seek(pos); + } else { + //add chunk lines + if (manifest_sections.has(text)) { + for (List::Element *E = manifest_sections[text].front(); E; E = E->next()) { + new_file += E->get() + "\n"; + } + } + new_file += end_marker + "\n"; + } + } else { + new_file += l + "\n"; //pass line by + } + + } else if (l.strip_edges().begins_with("::Element *E = manifest_sections["application_tags"].front(); E; E = E->next()) { + String to_add = E->get().strip_edges(); + base += " " + to_add + " "; + } + } + base += ">\n"; + new_file += base; + } + } else { + new_file += l + "\n"; + } + } + } + } + + FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::WRITE); + f->store_string(new_file); + f->close(); + } + } + virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); @@ -1481,21 +1765,86 @@ public: EditorProgress ep("export", "Exporting for Android", 105); - if (p_debug) - src_apk = p_preset->get("custom_package/debug"); - else - src_apk = p_preset->get("custom_package/release"); + if (bool(p_preset->get("custom_package/use_custom_build"))) { //custom build + //re-generate build.gradle and AndroidManifest.xml - src_apk = src_apk.strip_edges(); - if (src_apk == "") { - if (p_debug) { - src_apk = find_export_template("android_debug.apk"); - } else { - src_apk = find_export_template("android_release.apk"); + { //test that installed build version is alright + FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ); + if (!f) { + EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); + return ERR_UNCONFIGURED; + } + String version = f->get_line().strip_edges(); + if (version != VERSION_FULL_CONFIG) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); + return ERR_UNCONFIGURED; + } } + //build project if custom build is enabled + String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path"); + + ERR_FAIL_COND_V(sdk_path == "", ERR_UNCONFIGURED); + + _update_custom_build_project(); + + OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required + + String build_command; +#ifdef WINDOWS_ENABLED + build_command = "gradlew.bat"; +#else + build_command = "gradlew"; +#endif + + String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build"); + + build_command = build_path.plus_file(build_command); + + List cmdline; + cmdline.push_back("build"); + cmdline.push_back("-p"); + cmdline.push_back(build_path); + /*{ used for debug + int ec; + String pipe; + OS::get_singleton()->execute(build_command, cmdline, true, NULL, NULL, &ec); + print_line("exit code: " + itos(ec)); + } + */ + int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline); + if (result != 0) { + EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation.")); + return ERR_CANT_CREATE; + } + if (p_debug) { + src_apk = build_path.plus_file("build/outputs/apk/debug/build-debug-unsigned.apk"); + } else { + src_apk = build_path.plus_file("build/outputs/apk/release/build-release-unsigned.apk"); + } + + if (!FileAccess::exists(src_apk)) { + EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk); + return ERR_CANT_CREATE; + } + + } else { + + if (p_debug) + src_apk = p_preset->get("custom_package/debug"); + else + src_apk = p_preset->get("custom_package/release"); + + src_apk = src_apk.strip_edges(); if (src_apk == "") { - EditorNode::add_io_error("Package not found: " + src_apk); - return ERR_FILE_NOT_FOUND; + if (p_debug) { + src_apk = find_export_template("android_debug.apk"); + } else { + src_apk = find_export_template("android_release.apk"); + } + if (src_apk == "") { + EditorNode::add_io_error("Package not found: " + src_apk); + return ERR_FILE_NOT_FOUND; + } } } @@ -1975,6 +2324,8 @@ void register_android_exporter() { EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey"); EDITOR_DEF("export/android/debug_keystore_pass", "android"); EDITOR_DEF("export/android/force_system_user", false); + EDITOR_DEF("export/android/custom_build_sdk_path", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/custom_build_sdk_path", PROPERTY_HINT_GLOBAL_DIR, "*.keystore")); EDITOR_DEF("export/android/timestamping_authority_url", ""); EDITOR_DEF("export/android/shutdown_adb_on_exit", true); diff --git a/platform/android/AndroidManifest.xml.template b/platform/android/java/AndroidManifest.xml similarity index 55% rename from platform/android/AndroidManifest.xml.template rename to platform/android/java/AndroidManifest.xml index daaf847f116..29ddd844baa 100644 --- a/platform/android/AndroidManifest.xml.template +++ b/platform/android/java/AndroidManifest.xml @@ -11,11 +11,20 @@ android:largeScreens="true" android:xlargeScreens="true"/> + + -$$ADD_PERMISSION_CHUNKS$$ + + + + + + + + + - -$$ADD_APPLICATION_CHUNKS$$ + + + + + + + android:targetPackage="org.godotengine.game" /> diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle new file mode 100644 index 00000000000..868bbee8314 --- /dev/null +++ b/platform/android/java/build.gradle @@ -0,0 +1,115 @@ +//Gradle project for Godot Engine Android port. +//Do not modify code between the BEGIN/END sections, as it's autogenerated by add-ons + +buildscript { + repositories { + google() + jcenter() +//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN +//CHUNK_BUILDSCRIPT_REPOSITORIES_END + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' +//CHUNK_BUILD_DEPENDENCIES_BEGIN +//CHUNK_BUILD_DEPENDENCIES_END + } +} + +apply plugin: 'com.android.application' + +allprojects { + repositories { + mavenCentral() + google() + jcenter() +//CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN +//CHUNK_ALLPROJECTS_REPOSITORIES_END + + } +} + +dependencies { + implementation "com.android.support:support-core-utils:28.0.0" +//CHUNK_DEPENDENCIES_BEGIN +compile ('com.google.android.gms:play-services-ads:16.0.0') { exclude group: 'com.android.support' } +//CHUNK_DEPENDENCIES_END +} + +android { + + lintOptions { + abortOnError false + disable 'MissingTranslation','UnusedResources' + } + + compileSdkVersion 28 + buildToolsVersion "28.0.3" + useLibrary 'org.apache.http.legacy' + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } + defaultConfig { + minSdkVersion 18 + targetSdkVersion 28 +//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN +//CHUNK_ANDROID_DEFAULTCONFIG_END + } + // Both signing and zip-aligning will be done at export time + buildTypes.all { buildType -> + buildType.zipAlignEnabled false + buildType.signingConfig null + } + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src' +//DIR_SRC_BEGIN +,'/home/red/coding/godot-demos/2d/platformer/android/admob/src' +//DIR_SRC_END + ] + res.srcDirs = [ + 'res' +//DIR_RES_BEGIN +//DIR_RES_END + ] + aidl.srcDirs = [ + 'aidl' +//DIR_AIDL_BEGIN +//DIR_AIDL_END + ] + assets.srcDirs = [ + 'assets' +//DIR_ASSETS_BEGIN +//DIR_ASSETS_END + + ] + } + debug.jniLibs.srcDirs = [ + 'libs/debug' +//DIR_JNI_DEBUG_BEGIN +//DIR_JNI_DEBUG_END + ] + release.jniLibs.srcDirs = [ + 'libs/release' +//DIR_JNI_RELEASE_BEGIN +//DIR_JNI_RELEASE_END + ] + } +// No longer used, as it's not useful for build source template +// applicationVariants.all { variant -> +// variant.outputs.all { output -> +// output.outputFileName = "../../../../../../../bin/android_${variant.name}.apk" +// } +// } + +} + +//CHUNK_GLOBAL_BEGIN +//CHUNK_GLOBAL_END + + + + + diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 5e121066390..0c3d8af2209 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -2460,7 +2460,7 @@ void OS_Windows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, DeleteDC(hMainDC); } -Error OS_Windows::execute(const String &p_path, const List &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr) { +Error OS_Windows::execute(const String &p_path, const List &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { if (p_blocking && r_pipe) { @@ -2479,7 +2479,13 @@ Error OS_Windows::execute(const String &p_path, const List &p_arguments, char buf[65535]; while (fgets(buf, 65535, f)) { + if (p_pipe_mutex) { + p_pipe_mutex->lock(); + } (*r_pipe) += buf; + if (p_pipe_mutex) { + p_pipe_mutex->lock(); + } } int rv = _pclose(f); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index c15e1cabc31..0e0b9bf3f69 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -261,7 +261,7 @@ public: virtual void delay_usec(uint32_t p_usec) const; virtual uint64_t get_ticks_usec() const; - virtual Error execute(const String &p_path, const List &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false); + virtual Error execute(const String &p_path, const List &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL); virtual Error kill(const ProcessID &p_pid); virtual int get_process_id() const;